/*
* Created on Jun 6, 2004
*
* Paros and its related class files.
*
* Paros is an HTTP/HTTPS proxy for assessing web application security.
* Copyright (C) 2003-2004 Chinotec Technologies Company
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Clarified Artistic License
* as published by the Free Software Foundation.
*
* 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
* Clarified Artistic License for more details.
*
* You should have received a copy of the Clarified Artistic License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
// ZAP: 2012/04/25 Added @Override annotation to the appropriate method.
// ZAP: 2012/11/04 Issue 408: Add support to encoding transformations, added an
// option to control whether the "Accept-Encoding" request-header field is
// modified/removed or not.
// ZAP: 2013/01/23 Clean up of exception handling/logging.
// ZAP: 2013/03/03 Issue 546: Remove all template Javadoc comments
// ZAP: 2014/01/22 Add the possibility to bound the proxy to all interfaces if null IP address has been set
// ZAP: 2014/03/06 Issue 1063: Add option to decode all gzipped content
// ZAP: 2014/03/23 Issue 968: Allow to choose the enabled SSL/TLS protocols
// ZAP: 2014/03/23 Issue 1017: Proxy set to 0.0.0.0 causes incorrect PAC file to be generated
// ZAP: 2015/11/04 Issue 1920: Report the host:port ZAP is listening on in daemon mode, or exit if it cant
// ZAP: 2016/06/13 Change option "Accept-Encoding" request-header to Remove Unsupported Encodings
// ZAP: 2017/03/26 Allow to configure if the proxy is behind NAT.
package org.parosproxy.paros.core.proxy;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.configuration.ConversionException;
import org.apache.commons.configuration.HierarchicalConfiguration;
import org.apache.log4j.Logger;
import org.parosproxy.paros.common.AbstractParam;
import org.parosproxy.paros.network.SSLConnector;
/**
* @author simon
*
*/
public class ProxyParam extends AbstractParam {
// private static final String PROXY = "proxy";
private static final Logger logger = Logger.getLogger(ProxyParam.class);
private static final String PROXY_BASE_KEY = "proxy";
private static final String PROXY_IP = "proxy.ip";
private static final String PROXY_PORT = "proxy.port";
// private static final String PROXY_SSL_IP = "proxy.SSLIp";
// private static final String PROXY_SSL_PORT = "proxy.SSLPort";
private static final String USE_REVERSE_PROXY = "proxy.reverseProxy.use";
private static final String REVERSE_PROXY_IP = "proxy.reverseProxy.ip";
private static final String REVERSE_PROXY_HTTP_PORT = "proxy.reverseProxy.httpPort";
private static final String REVERSE_PROXY_HTTPS_PORT = "proxy.reverseProxy.httpsPort";
/**
* The configuration key to save/load the option {@link #behindNat}.
*/
private static final String PROXY_BEHIND_NAT = PROXY_BASE_KEY + ".behindnat";
private static final String SECURITY_PROTOCOLS_ENABLED = PROXY_BASE_KEY + ".securityProtocolsEnabled";
private static final String ALL_SECURITY_PROTOCOLS_ENABLED_KEY = SECURITY_PROTOCOLS_ENABLED + ".protocol";
/**
* The configuration key to save/load the option {@link #removeUnsupportedEncodings}.
*/
private static final String REMOVE_UNSUPPORTED_ENCODINGS = "proxy.removeUnsupportedEncodings";
/**
* The configuration key for the option that controls whether the proxy
* should always decode gzipped content or not.
*/
private static final String ALWAYS_DECODE_GZIP = "proxy.decodeGzip";
private String proxyIp = "localhost";
private int proxyPort = 8080;
private int proxySSLPort = 8443;
private int useReverseProxy = 0;
private String reverseProxyIp = "localhost";
private int reverseProxyHttpPort = 80;
private int reverseProxyHttpsPort = 443;
/**
* Flag that indicates if the proxy ip is any local address.
*
* @see #parse()
*/
private boolean proxyIpAnyLocalAddress;
/**
* Flag that controls whether or not the local proxy should remove unsupported encodings from the "Accept-Encoding"
* request-header field.
* <p>
* Default is {@code true}.
*
* @see #REMOVE_UNSUPPORTED_ENCODINGS
* @see #setRemoveUnsupportedEncodings(boolean)
*/
private boolean removeUnsupportedEncodings = true;
/**
* The option that controls whether the proxy should always decode gzipped content or not.
*/
private boolean alwaysDecodeGzip = true;
/**
* Flag that controls whether or not the local proxy is behind NAT.
* <p>
* Default is {@code false}.
*/
private boolean behindNat;
private String[] securityProtocolsEnabled;
public ProxyParam() {
}
@Override
protected void parse() {
proxyIp = getConfig().getString(PROXY_IP, "localhost");
determineProxyIpAnyLocalAddress();
try {
proxyPort = getConfig().getInt(PROXY_PORT, 8080);
} catch (Exception e) {
}
try {
proxySSLPort = 8443; //getConfig().getInt(PROXY_SSL_PORT, 8443);
} catch (Exception e) {
}
reverseProxyIp = getConfig().getString(REVERSE_PROXY_IP);
if (reverseProxyIp.equalsIgnoreCase("localhost") || reverseProxyIp.equalsIgnoreCase("127.0.0.1")) {
try {
reverseProxyIp = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e1) {
logger.error(e1.getMessage(), e1);
}
}
reverseProxyHttpPort = getConfig().getInt(REVERSE_PROXY_HTTP_PORT, 80);
reverseProxyHttpsPort = getConfig().getInt(REVERSE_PROXY_HTTPS_PORT, 443);
useReverseProxy = getConfig().getInt(USE_REVERSE_PROXY, 0);
removeUnsupportedEncodings = getConfig().getBoolean(REMOVE_UNSUPPORTED_ENCODINGS, true);
alwaysDecodeGzip = getConfig().getBoolean(ALWAYS_DECODE_GZIP, true);
loadSecurityProtocolsEnabled();
try {
behindNat = getConfig().getBoolean(PROXY_BEHIND_NAT, false);
} catch (ConversionException e) {
logger.error("Failed to read '" + PROXY_BEHIND_NAT + "'", e);
}
}
public String getProxyIp() {
if (isProxyIpAnyLocalAddress()) {
try {
return InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException ex) {
return "localhost";
}
} else {
return proxyIp;
}
}
// ZAP: added a method for nullable proxy...
public String getRawProxyIP() {
if (proxyIp == null || proxyIp.length() == 0) {
return "0.0.0.0";
}
return proxyIp;
}
public void setProxyIp(String proxyIp) {
if (this.proxyIp.equals(proxyIp)) {
return;
}
this.proxyIp = proxyIp.trim();
getConfig().setProperty(PROXY_IP, this.proxyIp);
determineProxyIpAnyLocalAddress();
}
public int getProxyPort() {
return proxyPort;
}
public void setProxyPort(int proxyPort) {
this.proxyPort = proxyPort;
getConfig().setProperty(PROXY_PORT, Integer.toString(this.proxyPort));
}
public int getProxySSLPort() {
return proxySSLPort;
}
// public void setProxySSLPort(int proxySSLPort) {
// this.proxySSLPort = proxySSLPort;
// getConfig().setProperty(PROXY_SSL_PORT, Integer.toString(this.proxySSLPort));
// }
public String getReverseProxyIp() {
return reverseProxyIp;
}
public void setReverseProxyIp(String reverseProxyIp) {
this.reverseProxyIp = reverseProxyIp.trim();
getConfig().setProperty(REVERSE_PROXY_IP, this.reverseProxyIp);
}
public int getReverseProxyHttpPort() {
return reverseProxyHttpPort;
}
public void setReverseProxyHttpPort(int reverseProxyHttpPort) {
this.reverseProxyHttpPort = reverseProxyHttpPort;
getConfig().setProperty(REVERSE_PROXY_HTTP_PORT, Integer.toString(this.reverseProxyHttpPort));
}
public int getReverseProxyHttpsPort() {
return reverseProxyHttpsPort;
}
public void setReverseProxyHttpsPort(int reverseProxyHttpsPort) {
this.reverseProxyHttpsPort = reverseProxyHttpsPort;
getConfig().setProperty(REVERSE_PROXY_HTTPS_PORT, Integer.toString(this.reverseProxyHttpsPort));
}
public boolean isUseReverseProxy() {
return (useReverseProxy != 0);
}
public void setUseReverseProxy(boolean isUse) {
if (isUse) {
useReverseProxy = 1;
getConfig().setProperty(USE_REVERSE_PROXY, Integer.toString(useReverseProxy));
return;
}
useReverseProxy = 0;
getConfig().setProperty(USE_REVERSE_PROXY, Integer.toString(useReverseProxy));
}
/**
* Sets whether the proxy should modify/remove the "Accept-Encoding"
* request-header field or not.
*
* @param modifyAcceptEncodingHeader {@code true} if the proxy should
* modify/remove the "Accept-Encoding" request-header field, {@code false}
* otherwise
* @deprecated (2.6.0) Use {@link #setRemoveUnsupportedEncodings(boolean)} instead.
* @since 2.0.0
*/
@Deprecated
public void setModifyAcceptEncodingHeader(boolean modifyAcceptEncodingHeader) {
setRemoveUnsupportedEncodings(modifyAcceptEncodingHeader);
}
/**
* Tells whether the proxy should modify/remove the "Accept-Encoding"
* request-header field or not.
*
* @return {@code true} if the proxy should modify/remove the
* "Accept-Encoding" request-header field, {@code false} otherwise
* @deprecated (2.6.0) Use {@link #isRemoveUnsupportedEncodings()} instead.
* @since 2.0.0
*/
@Deprecated
public boolean isModifyAcceptEncodingHeader() {
return isRemoveUnsupportedEncodings();
}
/**
* Sets whether or not the local proxy should remove unsupported encodings from the "Accept-Encoding" request-header field.
* <p>
* The whole header is removed if empty after the removal of unsupported encodings.
*
* @param remove {@code true} if the local proxy should remove unsupported encodings, {@code false} otherwise
* @since 2.6.0
* @see #isRemoveUnsupportedEncodings()
*/
public void setRemoveUnsupportedEncodings(boolean remove) {
if (removeUnsupportedEncodings != remove) {
this.removeUnsupportedEncodings = remove;
getConfig().setProperty(REMOVE_UNSUPPORTED_ENCODINGS, Boolean.valueOf(removeUnsupportedEncodings));
}
}
/**
* Tells whether or not the local proxy should remove unsupported encodings from the "Accept-Encoding" request-header field.
* <p>
* The whole header is removed if empty after the removal of unsupported encodings.
*
* @return {@code true} if the local proxy should remove unsupported encodings, {@code false} otherwise
* @since 2.6.0
* @see #setRemoveUnsupportedEncodings(boolean)
*/
public boolean isRemoveUnsupportedEncodings() {
return removeUnsupportedEncodings;
}
/**
* Tells whether the proxy should always decode gzipped content or not.
*
* @return {@code true} if the proxy should always decode gzipped content, {@code false} otherwise
*/
public boolean isAlwaysDecodeGzip() {
return alwaysDecodeGzip;
}
/**
* Sets whether the proxy should always decode gzipped content or not.
*
* @param alwaysDecodeGzip {@code true} if the proxy should
* always decode gzipped content, {@code false} otherwise
*/
public void setAlwaysDecodeGzip(boolean alwaysDecodeGzip) {
this.alwaysDecodeGzip = alwaysDecodeGzip;
getConfig().setProperty(ALWAYS_DECODE_GZIP, Boolean.valueOf(alwaysDecodeGzip));
}
/**
* Returns the security protocols enabled (SSL/TLS) for outgoing connections.
*
* @return the security protocols enabled for outgoing connections.
* @since 2.3.0
*/
public String[] getSecurityProtocolsEnabled() {
return Arrays.copyOf(securityProtocolsEnabled, securityProtocolsEnabled.length);
}
/**
* Sets the security protocols enabled (SSL/TLS) for outgoing connections.
* <p>
* The call has no effect if the given array is null or empty.
* </p>
*
* @param enabledProtocols the security protocols enabled (SSL/TLS) for outgoing connections.
* @throws IllegalArgumentException if at least one of the {@code enabledProtocols} is {@code null} or empty.
* @since 2.3.0
*/
public void setSecurityProtocolsEnabled(String[] enabledProtocols) {
if (enabledProtocols == null || enabledProtocols.length == 0) {
return;
}
for (int i= 0; i < enabledProtocols.length; i++) {
if (enabledProtocols[i] == null || enabledProtocols[i].isEmpty()) {
throw new IllegalArgumentException("The parameter enabledProtocols must not contain null or empty elements.");
}
}
((HierarchicalConfiguration) getConfig()).clearTree(ALL_SECURITY_PROTOCOLS_ENABLED_KEY);
for (int i = 0; i < enabledProtocols.length; ++i) {
String elementBaseKey = ALL_SECURITY_PROTOCOLS_ENABLED_KEY + "(" + i + ")";
getConfig().setProperty(elementBaseKey, enabledProtocols[i]);
}
this.securityProtocolsEnabled = Arrays.copyOf(enabledProtocols, enabledProtocols.length);
SSLConnector.setServerEnabledProtocols(enabledProtocols);
}
private void loadSecurityProtocolsEnabled() {
List<Object> protocols = getConfig().getList(ALL_SECURITY_PROTOCOLS_ENABLED_KEY);
if (protocols.size() != 0) {
securityProtocolsEnabled = new String[protocols.size()];
securityProtocolsEnabled = protocols.toArray(securityProtocolsEnabled);
SSLConnector.setServerEnabledProtocols(securityProtocolsEnabled);
} else {
setSecurityProtocolsEnabled(SSLConnector.getServerEnabledProtocols());
}
}
/**
* Tells whether or not the proxy IP is set to listen on any local address.
*
* @return {@code true} if the proxy IP is set to listen on any local address, {@code false} otherwise.
* @see #determineProxyIpAnyLocalAddress()
* @see #getProxyIp()
*/
public boolean isProxyIpAnyLocalAddress() {
return proxyIpAnyLocalAddress;
}
/**
* Determines if the proxy IP is set to listen on any local address and updates the corresponding flag accordingly.
* <p>
* The proxy IP is set to listen on any local address if it is either empty or the method
* {@code InetAddress#isAnyLocalAddress()} called on the proxy IP returns {@code true}.
*
* @see #proxyIpAnyLocalAddress
* @see #proxyIp
* @see #isProxyIpAnyLocalAddress()
* @see InetAddress#isAnyLocalAddress()
*/
private void determineProxyIpAnyLocalAddress() {
try {
proxyIpAnyLocalAddress = proxyIp.isEmpty() || InetAddress.getByName(proxyIp).isAnyLocalAddress();
} catch (UnknownHostException e) {
proxyIpAnyLocalAddress = false;
}
}
/**
* Tells whether or not the proxy is behind NAT.
*
* @return {@code true} if the proxy is behind NAT, {@code false} otherwise.
*/
public boolean isBehindNat() {
return behindNat;
}
/**
* Sets whether or not the proxy is behind NAT.
*
* @param behindNat {@code true} if the proxy is behind NAT, {@code false} otherwise.
*/
public void setBehindNat(boolean behindNat) {
this.behindNat = behindNat;
getConfig().setProperty(PROXY_BEHIND_NAT, Boolean.valueOf(behindNat));
}
}