diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigReseedHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigReseedHandler.java index ab5e83408..483bb9a7b 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigReseedHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigReseedHandler.java @@ -153,7 +153,10 @@ public class ConfigReseedHandler extends FormHandler { changes.put(Reseeder.PROP_SSL_DISABLE, Boolean.toString(disabled)); saveBoolean(Reseeder.PROP_PROXY_ENABLE, "enable"); - saveBoolean(Reseeder.PROP_SPROXY_ENABLE, "senable"); + String pmode = getJettyString("pmode"); + boolean senable = pmode != null && pmode.length() > 0; + changes.put(Reseeder.PROP_SPROXY_ENABLE, Boolean.toString(senable)); + saveString(Reseeder.PROP_SPROXY_TYPE, "pmode"); if (_context.router().saveConfig(changes, removes)) addFormNotice(_t("Configuration saved successfully.")); else diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigReseedHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigReseedHelper.java index 80907610c..ed2f83f07 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigReseedHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigReseedHelper.java @@ -60,6 +60,18 @@ public class ConfigReseedHelper extends HelperBase { return ""; } + /** @since 0.9.33 */ + public String pmodeChecked(int mode) { + String c = _context.getProperty(Reseeder.PROP_SPROXY_TYPE, "HTTP"); + boolean disabled = !_context.getBooleanProperty(Reseeder.PROP_SPROXY_ENABLE); + if ((mode == 0 && disabled) || + (mode == 1 && !disabled && c.equals("HTTP")) || + (mode == 2 && !disabled && c.equals("SOCKS4")) || + (mode == 3 && !disabled && c.equals("SOCKS5"))) + return CHECKED; + return ""; + } + public String getEnable() { return getChecked(Reseeder.PROP_PROXY_ENABLE); } @@ -69,9 +81,11 @@ public class ConfigReseedHelper extends HelperBase { return getChecked(Reseeder.PROP_PROXY_AUTH_ENABLE); } +/**** public String getSenable() { return getChecked(Reseeder.PROP_SPROXY_ENABLE); } +****/ /** @since 0.8.9 */ public String getSauth() { diff --git a/apps/routerconsole/jsp/configreseed.jsp b/apps/routerconsole/jsp/configreseed.jsp index e35902542..675a71c61 100644 --- a/apps/routerconsole/jsp/configreseed.jsp +++ b/apps/routerconsole/jsp/configreseed.jsp @@ -108,6 +108,31 @@
" />
+<% if (reseedHelper.shouldShowHTTPSProxy()) { %> +<%=intl._t("Reseed Proxy Type")%>: +
+
+
+ +<%=intl._t("HTTPS Proxy Host")%>: +" > +<%=intl._t("HTTPS Proxy Port")%>: +" > + + +<% } // shouldShowHTTPSProxy %> + <% if (reseedHelper.shouldShowHTTPProxy()) { %> > @@ -124,24 +149,6 @@ " > <% } // shouldShowHTTPProxy %> -<% if (reseedHelper.shouldShowHTTPSProxy()) { %> -<%=intl._t("Enable HTTPS Proxy?")%> - > -<%=intl._t("HTTPS Proxy Host")%>: -" > -<%=intl._t("HTTPS Proxy Port")%>: -" > - - -<% } // shouldShowHTTPSProxy %> - " /> diff --git a/core/java/src/net/i2p/util/EepGet.java b/core/java/src/net/i2p/util/EepGet.java index 33ad0b6d7..19c19e6f0 100644 --- a/core/java/src/net/i2p/util/EepGet.java +++ b/core/java/src/net/i2p/util/EepGet.java @@ -1737,6 +1737,12 @@ public class EepGet { rv.put("response", '"' + PasswordManager.md5Hex(kd) + '"'); return rv; } + + /** @since 0.9.33 */ + public String getUsername() { return username; } + + /** @since 0.9.33 */ + public String getPassword() { return password; } } /** diff --git a/core/java/src/net/i2p/util/SSLEepGet.java b/core/java/src/net/i2p/util/SSLEepGet.java index f5b04879a..d9e90e71b 100644 --- a/core/java/src/net/i2p/util/SSLEepGet.java +++ b/core/java/src/net/i2p/util/SSLEepGet.java @@ -71,6 +71,8 @@ import net.i2p.I2PAppContext; import net.i2p.crypto.CertUtil; import net.i2p.crypto.KeyStoreUtil; import net.i2p.data.DataHelper; +import net.i2p.socks.SOCKS4Client; +import net.i2p.socks.SOCKS5Client; /** * HTTPS only, no retries, no min and max size options, no timeout option @@ -104,7 +106,7 @@ public class SSLEepGet extends EepGet { * Not all may be supported. * @since 0.9.33 */ - public enum ProxyType { NONE, HTTP, HTTPS, INTERNAL, SOCKS4, SOCKS5 } + public enum ProxyType { NONE, HTTP, HTTPS, INTERNAL, SOCKS4, SOCKS5, TRANSPARENT } /** @@ -244,9 +246,10 @@ public class SSLEepGet extends EepGet { int saveCerts = 0; boolean noVerify = false; String proxyHost = "127.0.0.1"; - int proxyPort = 80; + int proxyPort = 0; + ProxyType ptype = ProxyType.HTTP; boolean error = false; - Getopt g = new Getopt("ssleepget", args, "p:sz"); + Getopt g = new Getopt("ssleepget", args, "p:y:sz"); try { int c; while ((c = g.getopt()) != -1) { @@ -265,6 +268,19 @@ public class SSLEepGet extends EepGet { } break; + case 'y': + String y = g.getOptarg().toUpperCase(Locale.US); + if (y.equals("HTTP") || y.equals("HTTPS")) { + // already set + } else if (y.equals("SOCKS4")) { + ptype = ProxyType.SOCKS4; + } else if (y.equals("SOCKS5")) { + ptype = ProxyType.SOCKS5; + } else { + error = true; + } + break; + case 's': saveCerts++; break; @@ -294,10 +310,17 @@ public class SSLEepGet extends EepGet { String saveAs = suggestName(url); SSLEepGet get; - if (proxyHost != null) - get = new SSLEepGet(I2PAppContext.getGlobalContext(), ProxyType.HTTP, proxyHost, proxyPort, saveAs, url); - else + if (proxyHost != null) { + if (proxyPort == 0) { + if (ptype == ProxyType.HTTP) + proxyPort = 8080; + else + proxyPort = 1080; + } + get = new SSLEepGet(I2PAppContext.getGlobalContext(), ptype, proxyHost, proxyPort, saveAs, url); + } else { get = new SSLEepGet(I2PAppContext.getGlobalContext(), saveAs, url); + } if (saveCerts > 0) get._saveCerts = saveCerts; if (noVerify) @@ -309,8 +332,9 @@ public class SSLEepGet extends EepGet { } private static void usage() { - System.err.println("Usage: SSLEepGet [-psz] https://url\n" + - " -p proxyHost[:proxyPort] // default port 80\n" + + System.err.println("Usage: SSLEepGet [-psyz] https://url\n" + + " -p proxyHost[:proxyPort] // default port 8080 for HTTPS and 1080 for SOCKS\n" + + " -y HTTPS|SOCKS4|SOCKS5 // proxy type, default HTTPS if proxyHost is set\n" + " -s save unknown certs\n" + " -s -s save all certs\n" + " -z bypass hostname verification"); @@ -667,9 +691,35 @@ public class SSLEepGet extends EepGet { port = 443; if (_shouldProxy) { - if (_proxyType != ProxyType.HTTP) + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Connecting to " + _proxyType + " proxy"); + switch (_proxyType) { + case HTTP: + httpProxyConnect(host, port); + break; + + case SOCKS4: + socksProxyConnect(false, host, port); + break; + + case SOCKS5: + socksProxyConnect(true, host, port); + break; + + case HTTPS: + case INTERNAL: + case TRANSPARENT: + default: throw new IOException("Unsupported proxy type " + _proxyType); - httpProxyConnect(host, port); + } + + // wrap the socket in an SSLSocket + if (_sslContext != null) + _proxy = _sslContext.getSocketFactory().createSocket(_proxy, host, port, true); + else + _proxy = ((SSLSocketFactory) SSLSocketFactory.getDefault()).createSocket(_proxy, host, port, true); + if (_log.shouldLog(Log.DEBUG)) + _log.debug(_proxyType + " proxy headers read completely"); } else { // Warning, createSocket() followed by connect(InetSocketAddress) // disables SNI, at least on Java 7. @@ -735,9 +785,12 @@ public class SSLEepGet extends EepGet { /** * Connect to a HTTP proxy. + * Proxy address must be in _proxyHost and _proxyPort. * Side effects: Sets _proxy, _proxyIn, _proxyOut, * and other globals via readHeaders() * + * @param host what the proxy should connect to + * @param port what the proxy should connect to * @since 0.9.33 */ private void httpProxyConnect(String host, int port) throws IOException { @@ -777,20 +830,44 @@ public class SSLEepGet extends EepGet { throw new IOException("Timed out reading the proxy headers"); if (_responseCode == 407) { // TODO - throw new IOException("Proxy auth unsupported"); + throw new IOException("Authorization unsupported on HTTP Proxy"); } else if (_responseCode != 200) { // readHeaders() will throw on most errors, but here we ensure it is 200 throw new IOException("Invalid proxy response: " + _responseCode + ' ' + _responseText); } if (_redirectLocation != null) throw new IOException("Proxy redirect not allowed"); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("proxy headers read completely"); + } - // wrap the socket in an SSLSocket - if (_sslContext != null) - _proxy = _sslContext.getSocketFactory().createSocket(_proxy, host, port, true); - else - _proxy = ((SSLSocketFactory) SSLSocketFactory.getDefault()).createSocket(_proxy, host, port, true); + /** + * Connect to a SOCKS proxy. + * Proxy address must be in _proxyHost and _proxyPort. + * Side effects: Sets _proxy, _proxyIn, _proxyOut, + * and other globals via readHeaders() + * + * @param host what the proxy should connect to + * @param port what the proxy should connect to + * @since 0.9.33 + */ + private void socksProxyConnect(boolean isSocks5, String host, int port) throws IOException { + if (_fetchHeaderTimeout > 0) { + _proxy = new Socket(); + _proxy.setSoTimeout(_fetchHeaderTimeout); + _proxy.connect(new InetSocketAddress(_proxyHost, _proxyPort), _fetchHeaderTimeout); + } else { + _proxy = new Socket(_proxyHost, _proxyPort); + } + if (_authState != null) { + if (!isSocks5) + throw new IOException("Authorization unsupported on SOCKS 4"); + SOCKS5Client.connect(_proxy, host, port, _authState.getUsername(), _authState.getPassword()); + } else { + if (isSocks5) + SOCKS5Client.connect(_proxy, host, port); + else + SOCKS4Client.connect(_proxy, host, port); + } + _proxyIn = _proxy.getInputStream(); + _proxyOut = _proxy.getOutputStream(); } } diff --git a/history.txt b/history.txt index 7d0bbcd6f..3657535f1 100644 --- a/history.txt +++ b/history.txt @@ -1,5 +1,10 @@ -2017-11-16 zzz +2017-11-18 zzz + * Reseed: Add SOCKS proxy support (ticket #1130) + +2017-11-17 zzz + * Addressbook: Fix adding alternates after importing an empty book (ticket #2072) * Reseed: Add HTTPS proxy support (ticket #423) + * SOCKS: Move code from i2ptunnel to core, in prep for SSLEepGet use (ticket #1130) 2017-11-16 zzz * Console: Hide Reseed HTTP proxy options if no HTTP URLs (ticket #2007) diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 0725033fa..f6e6df3a5 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -18,7 +18,7 @@ public class RouterVersion { /** deprecated */ public final static String ID = "Monotone"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 3; + public final static long BUILD = 4; /** for example "-test" */ public final static String EXTRA = ""; diff --git a/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java b/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java index 8a7b68ca7..00c9c3e32 100644 --- a/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java +++ b/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java @@ -145,6 +145,8 @@ public class Reseeder { public static final String PROP_SPROXY_USERNAME = "router.reseedSSLProxy.username"; public static final String PROP_SPROXY_PASSWORD = "router.reseedSSLProxy.password"; public static final String PROP_SPROXY_AUTH_ENABLE = "router.reseedSSLProxy.authEnable"; + /** @since 0.9.33 */ + public static final String PROP_SPROXY_TYPE = "router.reseedSSLProxyType"; /** @since 0.9 */ public static final String PROP_DISABLE = "router.reseedDisable"; @@ -921,10 +923,11 @@ public class Reseeder { boolean ssl = url.toString().startsWith("https"); if (ssl) { boolean shouldProxy = _sproxyHost != null && _sproxyHost.length() > 0 && _sproxyPort > 0; + SSLEepGet.ProxyType ptype = getProxyType(); SSLEepGet sslget; if (_sslState == null) { if (shouldProxy) - sslget = new SSLEepGet(_context, SSLEepGet.ProxyType.HTTP, _sproxyHost, _sproxyPort, + sslget = new SSLEepGet(_context, ptype, _sproxyHost, _sproxyPort, baos, url.toString()); else sslget = new SSLEepGet(_context, baos, url.toString()); @@ -932,7 +935,7 @@ public class Reseeder { _sslState = sslget.getSSLState(); } else { if (shouldProxy) - sslget = new SSLEepGet(_context, SSLEepGet.ProxyType.HTTP, _sproxyHost, _sproxyPort, + sslget = new SSLEepGet(_context, ptype, _sproxyHost, _sproxyPort, baos, url.toString(), _sslState); else sslget = new SSLEepGet(_context, baos, url.toString(), _sslState); @@ -980,10 +983,11 @@ public class Reseeder { boolean ssl = url.toString().startsWith("https"); if (ssl) { boolean shouldProxy = _sproxyHost != null && _sproxyHost.length() > 0 && _sproxyPort > 0; + SSLEepGet.ProxyType ptype = getProxyType(); SSLEepGet sslget; if (_sslState == null) { if (shouldProxy) - sslget = new SSLEepGet(_context, SSLEepGet.ProxyType.HTTP, _sproxyHost, _sproxyPort, + sslget = new SSLEepGet(_context, ptype, _sproxyHost, _sproxyPort, out.getPath(), url.toString()); else sslget = new SSLEepGet(_context, out.getPath(), url.toString()); @@ -991,7 +995,7 @@ public class Reseeder { _sslState = sslget.getSSLState(); } else { if (shouldProxy) - sslget = new SSLEepGet(_context, SSLEepGet.ProxyType.HTTP, _sproxyHost, _sproxyPort, + sslget = new SSLEepGet(_context, ptype, _sproxyHost, _sproxyPort, out.getPath(), url.toString(), _sslState); else sslget = new SSLEepGet(_context, out.getPath(), url.toString(), _sslState); @@ -1060,6 +1064,19 @@ public class Reseeder { return true; } + /** + * @throws IOException if unknown, default is HTTP + * @return non-null + * @since 0.9.33 + */ + private SSLEepGet.ProxyType getProxyType() throws IOException { + String sptype = _context.getProperty(PROP_SPROXY_TYPE, "HTTP").toUpperCase(Locale.US); + try { + return SSLEepGet.ProxyType.valueOf(sptype); + } catch (IllegalArgumentException iae) { + throw new IOException("Unsupported proxy type " + sptype); + } + } } private static final String BUNDLE_NAME = "net.i2p.router.web.messages";