// Copyright 2012 Giuseppe Iacono, Felipe Munoz Castillo
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.fides;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import net.grinder.plugin.http.tcpproxyfilter.ConnectionCache;
import net.grinder.plugin.http.tcpproxyfilter.ConnectionHandlerFactoryImplementation;
import net.grinder.plugin.http.tcpproxyfilter.HTTPRecordingImplementation;
import net.grinder.plugin.http.tcpproxyfilter.HTTPRequestFilter;
import net.grinder.plugin.http.tcpproxyfilter.HTTPResponseFilter;
import net.grinder.plugin.http.tcpproxyfilter.ProcessHTTPRecordingWithXSLT;
import net.grinder.plugin.http.tcpproxyfilter.ProcessHTTPRecordingWithXSLT.BuiltInStyleSheet;
import net.grinder.plugin.http.tcpproxyfilter.ProcessHTTPRecordingWithXSLT.StyleSheetFile;
import net.grinder.plugin.http.tcpproxyfilter.RegularExpressionsImplementation;
import net.grinder.tools.tcpproxy.CommentSourceImplementation;
import net.grinder.tools.tcpproxy.CompositeFilter;
import net.grinder.tools.tcpproxy.ConnectionDetails;
import net.grinder.tools.tcpproxy.EchoFilter;
import net.grinder.tools.tcpproxy.EndPoint;
import net.grinder.tools.tcpproxy.HTTPProxyTCPProxyEngine;
import net.grinder.tools.tcpproxy.NullFilter;
import net.grinder.tools.tcpproxy.PortForwarderTCPProxyEngine;
import net.grinder.tools.tcpproxy.TCPProxyConsole;
import net.grinder.tools.tcpproxy.TCPProxyEngine;
import net.grinder.tools.tcpproxy.TCPProxyFilter;
import net.grinder.tools.tcpproxy.TCPProxySSLSocketFactory;
import net.grinder.tools.tcpproxy.TCPProxySSLSocketFactoryImplementation;
import net.grinder.tools.tcpproxy.UpdatableCommentSource;
import net.grinder.util.AbstractMainClass;
import net.grinder.util.AttributeStringParserImplementation;
import net.grinder.util.SimpleStringEscaper;
import net.grinder.util.http.URIParserImplementation;
import org.picocontainer.DefaultPicoContainer;
import org.picocontainer.PicoContainer;
import org.picocontainer.behaviors.Caching;
import org.picocontainer.monitors.ConsoleComponentMonitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Run TCPProxy process.
*
* @goal TCPProxy
*
* @author Giuseppe Iacono
*/
public class GrinderTCPProxy extends GrinderPropertiesConfigure
{
public final static class TCPProxy extends AbstractMainClass
{
private static final String USAGE = " java "
+ TCPProxy.class.getName()
+ " <options>"
+ "\n\n"
+ "Commonly used options:"
+ "\n [-http [oldjython|jython|clojure|<stylesheet>]]"
+ "\n See below."
+ "\n [-console] Display the console."
+ "\n [-requestfilter <filter>] Add a request filter."
+ "\n [-responsefilter <filter>] Add a response filter."
+ "\n [-localhost <host name/ip>] Default is localhost."
+ "\n [-localport <port>] Default is 8001."
+ "\n [-keystore <file>] Key store details for"
+ "\n [-keystorepassword <pass>] SSL certificates."
+ "\n [-keystoretype <type>] Default is JSSE dependent."
+ "\n\n"
+ "Other options:"
+ "\n [-properties <file>] Properties to pass to the filters."
+ "\n [-remotehost <host name>] Default is localhost."
+ "\n [-remoteport <port>] Default is 7001."
+ "\n [-timeout <seconds>] Proxy engine timeout."
+ "\n [-httpproxy <host> <port>] Route via HTTP/HTTPS proxy."
+ "\n [-httpsproxy <host> <port>] Override -httpproxy settings for"
+ "\n HTTPS."
+ "\n [-ssl] Use SSL when port forwarding."
+ "\n [-colour] Be pretty on ANSI terminals."
+ "\n [-component <class>] Register a component class with"
+ "\n the filter PicoContainer."
+ "\n [-debug] Make PicoContainer chatty."
+ "\n\n"
+ "<filter> is the name of a class that implements "
+ TCPProxyFilter.class.getName()
+ " or one of NONE, ECHO. The default "
+ "is ECHO. Multiple filters can be specified for each stream."
+ "\n\n"
+ "By default, the TCPProxy listens as an HTTP/HTTPS Proxy on "
+ "<localhost:localport>."
+ "\n\n"
+ "If either -remotehost or -remoteport is specified, the TCPProxy "
+ "acts a simple port forwarder between <localhost:localport> and "
+ "<remotehost:remoteport>. Specify -ssl for SSL support."
+ "\n\n"
+ "-http sets up request and response filters to produce a test script "
+ "suitable for use with the HTTP plugin. The keywords 'oldjython', "
+ "'jython', or 'clojure' can be used to set the script language; or the "
+ "filename of an alternative XSLT style sheet can be provided. The "
+ "default is 'oldjython' which creates a Jython script based on the "
+ "traditional (non-DCR) instrumentation."
+ "\n\n"
+ "-timeout is how long the TCPProxy will wait for a request "
+ "before timing out and freeing the local port. The TCPProxy will "
+ "not time out if there are active connections."
+ "\n\n"
+ "-console displays a simple control window that allows the TCPProxy "
+ "to be shutdown cleanly. This is needed because some shells, e.g. "
+ "Cygwin bash, do not allow Java processes to be interrupted cleanly, "
+ "so filters cannot rely on standard shutdown hooks. "
+ "\n\n"
+ "-httpproxy and -httpsproxy allow output to be directed through "
+ "another HTTP/HTTPS proxy; this may help you reach the Internet. "
+ "These options are not supported in port forwarding mode."
+ "\n\n" + "Typical usage: " + "\n java " + TCPProxy.class
+ " -http -console > grinder.py" + "\n\n";
/**
* Entry point.
*
* @param args
* Command line arguments.
*/
public static void main(String[] args)
{
final Logger logger = LoggerFactory.getLogger("tcpproxy");
try {
final TCPProxy tcpProxy = new TCPProxy(args, logger);
tcpProxy.run();
} catch (LoggedInitialisationException e) {
System.exit(1);
} catch (Throwable e) {
logger.error("Could not initialise", e);
System.exit(2);
}
System.exit(0);
}
private final DefaultPicoContainer m_filterContainer = new DefaultPicoContainer(
new Caching());
private final TCPProxyEngine m_proxyEngine;
/**
* Package scope for unit tests.
*/
TCPProxy(String[] args, Logger logger) throws Exception
{
super(logger, USAGE);
String tcpProxyDirectory = GrinderPropertiesConfigure.getTCP_PROXY_DIRECTORY();
// make sure the fileOutput exists
File fileOutput = new File(tcpProxyDirectory);
if (fileOutput != null && !fileOutput.exists()) {
fileOutput.mkdirs();
}
String pathTest = tcpProxyDirectory + File.separator + "grinder.py";
OutputStream out = new FileOutputStream(pathTest);
final PrintWriter output = new PrintWriter(out);
m_filterContainer.addComponent(output);
m_filterContainer.addComponent(logger);
final UpdatableCommentSource commentSource = new CommentSourceImplementation();
m_filterContainer.addComponent(commentSource);
// Default values.
int localPort = 8001;
String remoteHost = "localhost";
String localHost = "localhost";
int remotePort = 7001;
boolean useSSLPortForwarding = false;
File keyStoreFile = null;
char[] keyStorePassword = null;
String keyStoreType = null;
boolean isHTTPProxy = true;
boolean console = false;
EndPoint chainedHTTPProxy = null;
EndPoint chainedHTTPSProxy = null;
int timeout = 0;
boolean useColour = false;
final FilterChain requestFilterChain = new FilterChain("request");
final FilterChain responseFilterChain = new FilterChain("response");
try {
// Parse 1.
for (int i = 0; i < args.length; i++) {
if ("-properties".equalsIgnoreCase(args[i])) {
final Properties properties = new Properties();
final FileInputStream in = new FileInputStream(
new File(args[++i]));
try {
properties.load(in);
} finally {
in.close();
}
System.getProperties().putAll(properties);
}
}
// Parse 2.
for (int i = 0; i < args.length; i++) {
if ("-requestfilter".equalsIgnoreCase(args[i])) {
requestFilterChain.add(args[++i]);
} else if ("-responsefilter".equalsIgnoreCase(args[i])) {
responseFilterChain.add(args[++i]);
} else if ("-component".equalsIgnoreCase(args[i])) {
final Class<?> componentClass;
try {
componentClass = Class.forName(args[++i]);
} catch (ClassNotFoundException e) {
throw barfError("Class '" + args[i]
+ "' not found.");
}
m_filterContainer.addComponent(componentClass);
} else if ("-http".equalsIgnoreCase(args[i])) {
requestFilterChain.add(HTTPRequestFilter.class);
responseFilterChain.add(HTTPResponseFilter.class);
m_filterContainer
.addComponent(AttributeStringParserImplementation.class);
m_filterContainer.addComponent(ConnectionCache.class);
m_filterContainer
.addComponent(ConnectionHandlerFactoryImplementation.class);
m_filterContainer
.addComponent(HTTPRecordingImplementation.class);
m_filterContainer
.addComponent(ProcessHTTPRecordingWithXSLT.class);
m_filterContainer
.addComponent(RegularExpressionsImplementation.class);
m_filterContainer
.addComponent(URIParserImplementation.class);
m_filterContainer
.addComponent(SimpleStringEscaper.class);
if (i + 1 < args.length && !args[i + 1].startsWith("-")) {
final String s = args[++i];
if ("oldjython".equals(s)) {
// Default.
} else if ("jython".equals(s)) {
m_filterContainer
.addComponent(BuiltInStyleSheet.Jython);
} else if ("clojure".equals(s)) {
m_filterContainer
.addComponent(BuiltInStyleSheet.Clojure);
} else {
m_filterContainer
.addComponent(new StyleSheetFile(
new File(s)));
}
}
} else if ("-localhost".equalsIgnoreCase(args[i])) {
localHost = args[++i];
} else if ("-localport".equalsIgnoreCase(args[i])) {
localPort = Integer.parseInt(args[++i]);
} else if ("-remotehost".equalsIgnoreCase(args[i])) {
remoteHost = args[++i];
isHTTPProxy = false;
} else if ("-remoteport".equalsIgnoreCase(args[i])) {
remotePort = Integer.parseInt(args[++i]);
isHTTPProxy = false;
} else if ("-ssl".equalsIgnoreCase(args[i])) {
useSSLPortForwarding = true;
} else if ("-keystore".equalsIgnoreCase(args[i])) {
keyStoreFile = new File(args[++i]);
} else if ("-keystorepassword".equalsIgnoreCase(args[i])
|| "-storepass".equalsIgnoreCase(args[i])) {
keyStorePassword = args[++i].toCharArray();
} else if ("-keystoretype".equalsIgnoreCase(args[i])
|| "-storetype".equalsIgnoreCase(args[i])) {
keyStoreType = args[++i];
} else if ("-timeout".equalsIgnoreCase(args[i])) {
timeout = Integer.parseInt(args[++i]) * 1000;
} else if ("-console".equalsIgnoreCase(args[i])) {
console = true;
} else if ("-colour".equalsIgnoreCase(args[i])
|| "-color".equalsIgnoreCase(args[i])) {
useColour = true;
} else if ("-properties".equalsIgnoreCase(args[i])) {
/* Already handled */
++i;
} else if ("-httpproxy".equalsIgnoreCase(args[i])) {
chainedHTTPProxy = new EndPoint(args[++i],
Integer.parseInt(args[++i]));
} else if ("-httpsproxy".equalsIgnoreCase(args[i])) {
chainedHTTPSProxy = new EndPoint(args[++i],
Integer.parseInt(args[++i]));
} else if ("-debug".equalsIgnoreCase(args[i])) {
m_filterContainer
.changeMonitor(new ConsoleComponentMonitor(
System.err));
} else if ("-initialtest".equalsIgnoreCase(args[i])) {
final String argument = i + 1 < args.length ? args[++i]
: "123";
throw barfError("-initialTest is no longer supported. "
+ "Use -DHTTPPlugin.initialTest=" + argument
+ " or the -properties option instead.");
} else {
throw barfUsage();
}
}
} catch (FileNotFoundException fnfe) {
throw barfError(fnfe.getMessage());
} catch (IndexOutOfBoundsException e) {
throw barfUsage();
} catch (NumberFormatException e) {
throw barfUsage();
}
if (timeout < 0) {
throw barfError("Timeout must be non-negative.");
}
final EndPoint localEndPoint = new EndPoint(localHost, localPort);
final EndPoint remoteEndPoint = new EndPoint(remoteHost, remotePort);
if (chainedHTTPSProxy == null && chainedHTTPProxy != null) {
chainedHTTPSProxy = chainedHTTPProxy;
}
if (chainedHTTPSProxy != null && !isHTTPProxy) {
throw barfError("Routing through a HTTP/HTTPS proxy is not supported "
+ "in port forwarding mode.");
}
final TCPProxyFilter requestFilter = requestFilterChain
.resolveFilter();
final TCPProxyFilter responseFilter = responseFilterChain
.resolveFilter();
final StringBuilder startMessage = new StringBuilder();
startMessage.append("Initialising as ");
if (isHTTPProxy) {
startMessage.append("an HTTP/HTTPS proxy");
} else {
if (useSSLPortForwarding) {
startMessage.append("an SSL port forwarder");
} else {
startMessage.append("a TCP port forwarder");
}
}
startMessage.append(" with the parameters:");
startMessage.append("\n Request filters: ");
startMessage.append(requestFilter);
startMessage.append("\n Response filters: ");
startMessage.append(responseFilter);
startMessage.append("\n Local address: " + localEndPoint);
if (!isHTTPProxy) {
startMessage.append("\n Remote address: "
+ remoteEndPoint);
}
if (chainedHTTPProxy != null) {
startMessage.append("\n HTTP proxy: "
+ chainedHTTPProxy);
}
if (chainedHTTPSProxy != null) {
startMessage.append("\n HTTPS proxy: "
+ chainedHTTPSProxy);
}
if (keyStoreFile != null) {
startMessage.append("\n Key store: ");
startMessage.append(keyStoreFile.toString());
// Key store password is optional.
if (keyStorePassword != null) {
startMessage.append("\n Key store password: ");
for (int i = 0; i < keyStorePassword.length; ++i) {
startMessage.append('*');
}
}
// Key store type can be null => use whatever
// KeyStore.getDefaultType() says (we can't print the default
// here without loading the JSSE).
if (keyStoreType != null) {
startMessage.append("\n Key store type: "
+ keyStoreType);
}
}
logger.info(startMessage.toString());
System.out.println("Valor de keyStoreFile: " + keyStoreFile);
final TCPProxySSLSocketFactory sslSocketFactory = keyStoreFile != null ? new TCPProxySSLSocketFactoryImplementation(
keyStoreFile, keyStorePassword, keyStoreType)
: new TCPProxySSLSocketFactoryImplementation();
m_filterContainer.start();
if (isHTTPProxy) {
m_proxyEngine = new HTTPProxyTCPProxyEngine(sslSocketFactory,
requestFilter, responseFilter, output, logger,
localEndPoint, useColour, timeout, chainedHTTPProxy,
chainedHTTPSProxy);
} else {
if (useSSLPortForwarding) {
m_proxyEngine = new PortForwarderTCPProxyEngine(
sslSocketFactory, requestFilter, responseFilter,
output, logger, new ConnectionDetails(
localEndPoint, remoteEndPoint, true),
useColour, timeout);
} else {
m_proxyEngine = new PortForwarderTCPProxyEngine(
requestFilter, responseFilter, output, logger,
new ConnectionDetails(localEndPoint,
remoteEndPoint, false), useColour, timeout);
}
}
if (console) {
new TCPProxyConsole(m_proxyEngine, commentSource);
}
logger.info("Engine initialised, listening on port " + localPort);
}
private void run()
{
final Runnable shutdown = new Runnable() {
private boolean m_stopped = false;
public synchronized void run() {
if (!m_stopped) {
m_stopped = true;
m_proxyEngine.stop();
m_filterContainer.stop();
m_filterContainer.dispose();
}
}
};
Runtime.getRuntime().addShutdownHook(new Thread(shutdown));
m_proxyEngine.run();
shutdown.run();
getLogger().info("Engine exited");
}
private final class FilterChain
{
private final String m_type;
private final List<String> m_filterKeys = new ArrayList<String>();
private int m_value;
public FilterChain(String type) {
m_type = type;
}
public void add(Class<? extends TCPProxyFilter> theClass)
{
final String key = m_type + ++m_value;
m_filterContainer.addComponent(key, theClass);
m_filterKeys.add(key);
}
@SuppressWarnings("unchecked")
public void add(String filterClassName)
throws LoggedInitialisationException
{
if ("NONE".equals(filterClassName)) {
add(NullFilter.class);
} else if ("ECHO".equals(filterClassName)) {
add(EchoFilter.class);
} else {
final Class<?> filterClass;
try {
filterClass = Class.forName(filterClassName);
} catch (ClassNotFoundException e) {
throw barfError("Class '" + filterClassName
+ "' not found.");
}
if (!TCPProxyFilter.class.isAssignableFrom(filterClass)) {
throw barfError("The class '" + filterClass.getName()
+ "' does not implement the interface: '"
+ TCPProxyFilter.class.getName() + "'.");
}
add((Class<? extends TCPProxyFilter>) filterClass);
}
}
public TCPProxyFilter resolveFilter()
{
if (m_filterKeys.size() == 0) {
add(EchoFilter.class);
}
final CompositeFilter result = new CompositeFilter();
for (String key : m_filterKeys) {
result.add((TCPProxyFilter) m_filterContainer
.getComponent(key));
}
return result;
}
}
/**
* Accessor for unit tests.
*/
PicoContainer getFilterContainer() {
return m_filterContainer;
}
}
@Override
protected String getJythonVersion() {
return GrinderPropertiesConfigure.GRINDER_JYTHON_VERSION;
}
public void execute()
{
String[] args = new String[2];
args[0] = "-console";
args[1] = "-http";
TCPProxy.main(args);
}
}