/*
* 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.tcp;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import org.owasp.proxy.daemon.TargetedConnectionHandler;
import org.owasp.proxy.ssl.EncryptedConnectionHandler;
import org.owasp.proxy.ssl.SSLContextSelector;
/**
* This {@link TargetedConnectionHandler} provides a framework for intercepting
* arbitrary TCP socket-based protocols.
*
* It establishes a connection to the indicated target, then instantiates a pair
* of threads that read from the accepted socket and the target socket, and
* notify the {@link StreamInterceptor} provided when data has been read, or the
* connection terminated.
*
* The basic sequence is as follows:
* <ul>
* <li>A pair of threads are created, one reading from the accepted socket, and
* the other reading from the socket connected to the target.</li>
* <li>The {@link StreamInterceptor#connected(StreamHandle, StreamHandle)}
* method is called, passing the {@link StreamHandle}'s responsible for reading
* from the accepted socket, and from the target (in that order)</li>
* <li>As each StreamHandle reads from its socket, it calls
* {@link StreamInterceptor#received(StreamHandle, byte[], int, int)} with the
* data read. The StreamHandle serves as a key to inform the StreamInterceptor
* which socket the data relates to.</li>
* <li>If there is an error reading from the socket, the StreamHandle calls
* {@link StreamInterceptor#readException(StreamHandle, IOException)}</li>
* <li>Finally, {@link StreamInterceptor#inputClosed(StreamHandle)} is always
* called to allow the StreamInterceptor to release any allocated resources.</li>
* </ul>
*
* Note that it is still possible to write data to the StreamHandle even after
* the input has been closed, to allow for modification of the last packet sent.
*
* @author rogan
*
*/
public class InterceptingConnectionHandler implements
TargetedConnectionHandler, EncryptedConnectionHandler {
private StreamInterceptor<InetSocketAddress, InetSocketAddress> interceptor;
private SSLContextSelector contextSelector;
public InterceptingConnectionHandler(
StreamInterceptor<InetSocketAddress, InetSocketAddress> interceptor) {
if (interceptor == null)
throw new IllegalArgumentException("Interceptor may not be null");
this.interceptor = interceptor;
}
public void setSSLContextSelector(SSLContextSelector contextSelector) {
this.contextSelector = contextSelector;
}
/*
* (non-Javadoc)
*
* @see
* org.owasp.proxy.daemon.TargetedConnectionHandler#handleConnection(java
* .net.Socket, java.net.InetSocketAddress)
*/
@Override
public void handleConnection(final Socket client, InetSocketAddress target)
throws IOException {
Socket server = new Socket(Proxy.NO_PROXY);
server.connect(target);
relay(client, server);
}
@Override
public void handleConnection(Socket client, InetSocketAddress target,
boolean ssl) throws IOException {
Socket server = new Socket(Proxy.NO_PROXY);
server.connect(target);
if (ssl) {
server = layerSsl(server, target);
}
relay(client, server);
}
private void relay(Socket client, Socket server) throws IOException {
InetSocketAddress clientLabel, serverLabel;
InputStream ci, si;
OutputStream so, co;
StreamRelay<InetSocketAddress, InetSocketAddress> sr;
clientLabel = (InetSocketAddress) client.getRemoteSocketAddress();
ci = client.getInputStream();
co = client.getOutputStream();
serverLabel = (InetSocketAddress) server.getRemoteSocketAddress();
si = server.getInputStream();
so = server.getOutputStream();
sr = new StreamRelay<InetSocketAddress, InetSocketAddress>(interceptor,
clientLabel, ci, co, serverLabel, si, so);
Runnable cch = new Closer(server);
Runnable sch = new Closer(client);
sr.setCloseHandlers(cch, sch);
sr.run();
}
private Socket layerSsl(Socket socket, InetSocketAddress target)
throws IOException {
if (contextSelector == null) {
throw new IllegalStateException(
"SSL Context Selector is null, SSL is not supported!");
}
SSLContext sslContext = contextSelector.select(target);
SSLSocketFactory factory = sslContext.getSocketFactory();
SSLSocket sslsocket = (SSLSocket) factory.createSocket(socket, socket
.getInetAddress().getHostName(), socket.getPort(), true);
// sslsocket.setEnabledProtocols(enabledProtocols);
sslsocket.setUseClientMode(true);
// sslsocket.setSoTimeout(soTimeout);
sslsocket.startHandshake();
return sslsocket;
}
private static class Closer implements Runnable {
private Socket socket;
public Closer(Socket socket) {
this.socket = socket;
}
public void run() {
try {
if (!socket.isOutputShutdown()
&& !(socket instanceof SSLSocket))
socket.shutdownOutput();
} catch (IOException e) {
try {
if (!socket.isOutputShutdown())
socket.close();
} catch (IOException ignore) {
}
}
}
}
}