diff --git a/android/src/androidTest/java/com/msopentech/thali/toronionproxy/TestTorInstaller.java b/android/src/androidTest/java/com/msopentech/thali/toronionproxy/TestTorInstaller.java index 6a5018c2..4415503f 100644 --- a/android/src/androidTest/java/com/msopentech/thali/toronionproxy/TestTorInstaller.java +++ b/android/src/androidTest/java/com/msopentech/thali/toronionproxy/TestTorInstaller.java @@ -15,7 +15,7 @@ public TestTorInstaller(Context context, File configDir) { } @Override - public InputStream openBridgesStream() throws IOException { + public InputStream openDefaultBridgesStream() throws IOException { return context.getResources().openRawResource(R.raw.bridges); } } diff --git a/android_tor_installer/src/main/java/com/msopentech/thali/android/installer/AndroidTorInstaller.java b/android_tor_installer/src/main/java/com/msopentech/thali/android/installer/AndroidTorInstaller.java index e50f81b4..00301e9a 100644 --- a/android_tor_installer/src/main/java/com/msopentech/thali/android/installer/AndroidTorInstaller.java +++ b/android_tor_installer/src/main/java/com/msopentech/thali/android/installer/AndroidTorInstaller.java @@ -27,11 +27,11 @@ * Installs Tor for an Android app. This is a wrapper around the TorResourceInstaller. * * Since this class only deals with installing Tor, it is up to the developer to implement - * the openBridgesStream which will give the bridges for pluggable transports. A + * the openDefaultBridgesStream which will give the bridges for pluggable transports. A * typical implementation looks like: * * - * public InputStream openBridgesStream() throws IOException { + * public InputStream openDefaultBridgesStream() throws IOException { * return context.getResources().openRawResource(R.raw.bridges); * } * diff --git a/java/src/main/java/com/msopentech/thali/java/toronionproxy/JavaTorInstaller.java b/java/src/main/java/com/msopentech/thali/java/toronionproxy/JavaTorInstaller.java index d8a164c6..e835d00d 100644 --- a/java/src/main/java/com/msopentech/thali/java/toronionproxy/JavaTorInstaller.java +++ b/java/src/main/java/com/msopentech/thali/java/toronionproxy/JavaTorInstaller.java @@ -115,7 +115,12 @@ public void updateTorConfigCustom(String content) throws IOException, TimeoutExc * TorSettings.hasBridges is flagged to false to avoid this method being called. */ @Override - public InputStream openBridgesStream() throws IOException { + public InputStream openDefaultBridgesStream() throws IOException { throw new UnsupportedOperationException(); } + + @Override + public boolean hasDefaultBridgesStream() { + return false; + } } diff --git a/universal/build.gradle b/universal/build.gradle index 43812d9c..fa237c73 100644 --- a/universal/build.gradle +++ b/universal/build.gradle @@ -15,7 +15,7 @@ dependencies { implementation 'org.slf4j:slf4j-api:1.7.25' implementation 'net.freehaven.tor.control:jtorctl:0.2' testCompile group: 'junit', name: 'junit', version: '4.12' -} + testCompile group: 'org.mockito', name: 'mockito-core', version: '3.1.0'} task sourcesJar(type:Jar){ from sourceSets.main.allSource diff --git a/universal/src/main/java/com/msopentech/thali/toronionproxy/BridgeType.java b/universal/src/main/java/com/msopentech/thali/toronionproxy/BridgeType.java new file mode 100644 index 00000000..fbdff470 --- /dev/null +++ b/universal/src/main/java/com/msopentech/thali/toronionproxy/BridgeType.java @@ -0,0 +1,5 @@ +package com.msopentech.thali.toronionproxy; + +public enum BridgeType { + MEEK_LITE, OBFS3, OBFS4 +} diff --git a/universal/src/main/java/com/msopentech/thali/toronionproxy/DefaultSettings.java b/universal/src/main/java/com/msopentech/thali/toronionproxy/DefaultSettings.java index 5f5adacb..a1605f53 100644 --- a/universal/src/main/java/com/msopentech/thali/toronionproxy/DefaultSettings.java +++ b/universal/src/main/java/com/msopentech/thali/toronionproxy/DefaultSettings.java @@ -1,6 +1,6 @@ package com.msopentech.thali.toronionproxy; -import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -13,9 +13,18 @@ public boolean disableNetwork() { return true; } + public String getDnsHost() { + return null; + } + @Override - public String dnsPort() { - return "5400"; + public Integer getDnsPort() { + return 5400; + } + + @Override + public List getCustomBridges() { + return Collections.emptyList(); } @Override @@ -39,13 +48,18 @@ public String getExitNodes() { } @Override - public int getHttpTunnelPort() { - return 8118; + public String getHttpTunnelHost() { + return null; + } + + @Override + public Integer getHttpTunnelPort() { + return null; } @Override - public List getListOfSupportedBridges() { - return new ArrayList<>(); + public List getBridgeTypes() { + return Collections.emptyList(); } @Override @@ -59,7 +73,7 @@ public String getProxyPassword() { } @Override - public String getProxyPort() { + public Integer getProxyPort() { return null; } @@ -69,7 +83,7 @@ public String getProxySocks5Host() { } @Override - public String getProxySocks5ServerPort() { + public Integer getProxySocks5ServerPort() { return null; } @@ -94,7 +108,7 @@ public String getRelayNickname() { } @Override - public int getRelayPort() { + public Integer getRelayPort() { return 9001; } @@ -165,7 +179,7 @@ public boolean hasTestSocks() { @Override public boolean isAutomapHostsOnResolve() { - return true; + return false; } @Override @@ -179,8 +193,13 @@ public boolean runAsDaemon() { } @Override - public String transPort() { - return "9040"; + public String getTransparentProxyAddress() { + return null; + } + + @Override + public Integer getTransparentProxyPort() { + return null; } @Override diff --git a/universal/src/main/java/com/msopentech/thali/toronionproxy/TorConfigBuilder.java b/universal/src/main/java/com/msopentech/thali/toronionproxy/TorConfigBuilder.java index e324d9f5..579f4032 100644 --- a/universal/src/main/java/com/msopentech/thali/toronionproxy/TorConfigBuilder.java +++ b/universal/src/main/java/com/msopentech/thali/toronionproxy/TorConfigBuilder.java @@ -18,9 +18,7 @@ import java.net.InetSocketAddress; import java.net.Socket; import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.Random; public final class TorConfigBuilder { @@ -38,7 +36,7 @@ public TorConfigBuilder(OnionProxyContext context) { * Updates the tor config for all methods annotated with SettingsConfig */ public TorConfigBuilder updateTorConfig() throws Exception { - for(Method method : getClass().getMethods()) { + for (Method method : getClass().getMethods()) { for (Annotation annotation : method.getAnnotations()) { if (annotation instanceof SettingsConfig) { method.invoke(this); @@ -62,7 +60,7 @@ private static boolean isLocalPortOpen(int port) { } catch (Exception e) { return false; } finally { - if (socket != null) { + if (socket != null) { try { socket.close(); } catch (Exception ee) { @@ -76,8 +74,7 @@ public String asString() { } public TorConfigBuilder automapHostsOnResolve() { - buffer.append("AutomapHostsOnResolve 1").append('\n'); - return this; + return writeTrueProperty("AutomapHostsOnResolve"); } @SettingsConfig @@ -92,49 +89,39 @@ public TorConfigBuilder bridge(String type, String config) { return this; } - public TorConfigBuilder bridgeCustom(String config) { - if (!isNullOrEmpty(config)) { - buffer.append("Bridge ").append(config).append('\n'); - } - return this; - } - - public TorConfigBuilder configurePluggableTransportsFromSettings(File pluggableTransportClient) throws IOException { - List supportedBridges = settings.getListOfSupportedBridges(); - if (pluggableTransportClient == null || !settings.hasBridges() || supportedBridges.size() < 1) { + public TorConfigBuilder configurePluggableTransports(File pluggableTransportClient, + List bridgeTypes) + throws IOException { + if (pluggableTransportClient == null || bridgeTypes == null || bridgeTypes.isEmpty()) { return this; } - - if (!pluggableTransportClient.exists() || !pluggableTransportClient.canExecute()) { + if (!pluggableTransportClient.exists()) { throw new IOException("Bridge binary does not exist: " + pluggableTransportClient .getCanonicalPath()); } - if (supportedBridges.contains("obfs3") || supportedBridges.contains("obfs4")) { - transportPluginObfs(pluggableTransportClient.getCanonicalPath()); + if (!pluggableTransportClient.canExecute()) { + throw new IOException("Bridge binary is not executable: " + pluggableTransportClient + .getCanonicalPath()); } - if (supportedBridges.contains("meek")) { - transportPluginMeek(pluggableTransportClient.getCanonicalPath()); + for (BridgeType bridgeType : bridgeTypes) { + writeBridgeTransport(pluggableTransportClient, bridgeType); } - String type = supportedBridges.contains("meek") ? "meek_lite" : "obfs4"; - addBridgesFromResources(type, 2); return this; } public TorConfigBuilder cookieAuthentication() { - buffer.append("CookieAuthentication 1 ").append('\n'); - buffer.append("CookieAuthFile ").append(context.getConfig().getCookieAuthFile().getAbsolutePath()).append("\n"); - return this; + return writeTrueProperty("CookieAuthentication") + .writeLine("CookieAuthFile", context.getConfig().getCookieAuthFile().getAbsolutePath()); } @SettingsConfig public TorConfigBuilder cookieAuthenticationFromSettings() { return settings.hasCookieAuthentication() ? cookieAuthentication() : this; } - + public TorConfigBuilder connectionPadding() { - buffer.append("ConnectionPadding 1").append('\n'); - return this; + return writeTrueProperty("ConnectionPadding"); } @SettingsConfig @@ -143,9 +130,7 @@ public TorConfigBuilder connectionPaddingFromSettings() { } public TorConfigBuilder controlPortWriteToFile(String controlPortFile) { - buffer.append("ControlPortWriteToFile ").append(controlPortFile).append('\n'); - buffer.append("ControlPort auto\n"); - return this; + return writeLine("ControlPortWriteToFile", controlPortFile).writeLine("ControlPort auto"); } @SettingsConfig @@ -153,21 +138,43 @@ public TorConfigBuilder controlPortWriteToFileFromConfig() { return controlPortWriteToFile(context.config.getControlPortFile().getAbsolutePath()); } - public TorConfigBuilder debugLogs() { - buffer.append("Log debug syslog").append('\n'); - buffer.append("Log info syslog").append('\n'); - buffer.append("SafeLogging 0").append('\n'); + /** + * A custom entry looks like + * + * + * 69.163.45.129:443 9F090DE98CA6F67DEEB1F87EFE7C1BFD884E6E2F + * + */ + public TorConfigBuilder customBridges(List bridges) { + for (String bridge : bridges) { + if (!isNullOrEmpty(bridge)) { + writeLine("Bridge " + bridge); + } + } return this; } + @SettingsConfig + public TorConfigBuilder customBridgesFromSettings() { + if (!settings.hasBridges() || !hasCustomBridges()) { + return this; + } + return customBridges(settings.getCustomBridges()); + } + + public TorConfigBuilder debugLogs() { + writeLine("Log debug syslog"); + writeLine("Log info syslog"); + return writeFalseProperty("SafeLogging"); + } + @SettingsConfig public TorConfigBuilder debugLogsFromSettings() { return settings.hasDebugLogs() ? debugLogs() : this; } public TorConfigBuilder disableNetwork() { - buffer.append("DisableNetwork 1").append('\n'); - return this; + return writeTrueProperty("DisableNetwork"); } @SettingsConfig @@ -175,75 +182,64 @@ public TorConfigBuilder disableNetworkFromSettings() { return settings.disableNetwork() ? disableNetwork() : this; } - public TorConfigBuilder dnsPort(String dnsPort) { - if (!isNullOrEmpty(dnsPort)) buffer.append("DNSPort ").append(dnsPort).append('\n'); - return this; + public TorConfigBuilder dnsPort(String dnsHost, Integer dnsPort) { + return writeAddress("DNSPort", dnsHost, dnsPort, null); } @SettingsConfig public TorConfigBuilder dnsPortFromSettings() { - return dnsPort(settings.dnsPort()); + return dnsPort(settings.getDnsHost(), settings.getDnsPort()); } public TorConfigBuilder dontUseBridges() { - buffer.append("UseBridges 0").append('\n'); - return this; + return writeFalseProperty("UseBridges"); } public TorConfigBuilder entryNodes(String entryNodes) { if (!isNullOrEmpty(entryNodes)) - buffer.append("EntryNodes ").append(entryNodes).append('\n'); + writeLine("EntryNodes", entryNodes); return this; } public TorConfigBuilder excludeNodes(String excludeNodes) { if (!isNullOrEmpty(excludeNodes)) - buffer.append("ExcludeNodes ").append(excludeNodes).append('\n'); + writeLine("ExcludeNodes", excludeNodes); return this; } public TorConfigBuilder exitNodes(String exitNodes) { if (!isNullOrEmpty(exitNodes)) - buffer.append("ExitNodes ").append(exitNodes).append('\n'); + writeLine("ExitNodes", exitNodes); return this; } public TorConfigBuilder geoIpFile(String path) { - if (!isNullOrEmpty(path)) buffer.append("GeoIPFile ").append(path).append('\n'); + if (!isNullOrEmpty(path)) + writeLine("GeoIPFile", path); return this; } public TorConfigBuilder geoIpV6File(String path) { - if (!isNullOrEmpty(path)) buffer.append("GeoIPv6File ").append(path).append('\n'); + if (!isNullOrEmpty(path)) + writeLine("GeoIPv6File", path); return this; } - public TorConfigBuilder httpTunnelPort(int port, String isolationFlags) { - buffer.append("HTTPTunnelPort ").append(port); - if (!isNullOrEmpty(isolationFlags)) { - buffer.append(" ").append(isolationFlags); - } - buffer.append('\n'); - return this; + public TorConfigBuilder httpTunnelPort(String host, Integer port, String isolationFlags) { + return writeAddress("HTTPTunnelPort", host, port, isolationFlags); } @SettingsConfig public TorConfigBuilder httpTunnelPortFromSettings() { - return httpTunnelPort(settings.getHttpTunnelPort(), + return httpTunnelPort(settings.getHttpTunnelHost(), settings.getHttpTunnelPort(), settings.hasIsolationAddressFlagForTunnel() ? "IsolateDestAddr" : null); } - public TorConfigBuilder line(String value) { - if (!isNullOrEmpty(value)) buffer.append(value).append("\n"); - return this; - } - public TorConfigBuilder makeNonExitRelay(String dnsFile, int orPort, String nickname) { - buffer.append("ServerDNSResolvConfFile ").append(dnsFile).append('\n'); - buffer.append("ORPort ").append(orPort).append('\n'); - buffer.append("Nickname ").append(nickname).append('\n'); - buffer.append("ExitPolicy reject *:*").append('\n'); - return this; + writeLine("ServerDNSResolvConfFile", dnsFile); + writeLine("ORPort", String.valueOf(orPort)); + writeLine("Nickname", nickname); + return writeLine("ExitPolicy reject *:*"); } /** @@ -273,9 +269,18 @@ public TorConfigBuilder nonExitRelayFromSettings() { return this; } + /** + * Write bridges from packaged bridge list if bridges option is enabled and if user has set desired bridge types. + *

+ * If the user has also defined custom bridges, these take precedence and default bridges will not be written. + */ + @SettingsConfig + public TorConfigBuilder defaultBridgesFromSettings() { + return defaultBridgesFromResources(settings.getBridgeTypes()); + } + public TorConfigBuilder proxyOnAllInterfaces() { - buffer.append("SocksListenAddress 0.0.0.0").append('\n'); - return this; + return writeLine("SocksListenAddress 0.0.0.0"); } @SettingsConfig @@ -284,9 +289,9 @@ public TorConfigBuilder proxyOnAllInterfacesFromSettings() { } /** - * Set socks5 proxy with no authentication. This can be set if yo uare using a VPN. + * Set socks5 proxy with no authentication. This can be set if you are using a VPN. */ - public TorConfigBuilder proxySocks5(String host, String port) { + public TorConfigBuilder proxySocks5(String host, Integer port) { buffer.append("socks5Proxy ").append(host).append(':').append(port).append('\n'); return this; } @@ -302,16 +307,16 @@ public TorConfigBuilder proxySocks5FromSettings() { * Sets proxyWithAuthentication information. If proxyType, proxyHost or proxyPort is empty, * then this method does nothing. */ - public TorConfigBuilder proxyWithAuthentication(String proxyType, String proxyHost, String + public TorConfigBuilder proxyWithAuthentication(String proxyType, String proxyHost, Integer proxyPort, String proxyUser, String proxyPass) { - if (!isNullOrEmpty(proxyType) && !isNullOrEmpty(proxyHost) && !isNullOrEmpty(proxyPort)) { + if (!isNullOrEmpty(proxyType) && !isNullOrEmpty(proxyHost) && proxyPort != null) { buffer.append(proxyType).append("Proxy ").append(proxyHost).append(':').append (proxyPort).append('\n'); if (proxyUser != null && proxyPass != null) { if (proxyType.equalsIgnoreCase("socks5")) { - buffer.append("Socks5ProxyUsername ").append(proxyUser).append('\n'); - buffer.append("Socks5ProxyPassword ").append(proxyPass).append('\n'); + writeLine("Socks5ProxyUsername", proxyUser); + writeLine("Socks5ProxyPassword", proxyPass); } else { buffer.append(proxyType).append("ProxyAuthenticator ").append(proxyUser) .append(':').append(proxyPort).append('\n'); @@ -335,7 +340,7 @@ public TorConfigBuilder proxyWithAuthenticationFromSettings() { public TorConfigBuilder reachableAddressPorts(String reachableAddressesPorts) { if (!isNullOrEmpty(reachableAddressesPorts)) - buffer.append("ReachableAddresses ").append(reachableAddressesPorts).append('\n'); + writeLine("ReachableAddresses", reachableAddressesPorts); return this; } @@ -347,8 +352,7 @@ public TorConfigBuilder reachableAddressesFromSettings() { } public TorConfigBuilder reducedConnectionPadding() { - buffer.append("ReducedConnectionPadding 1").append('\n'); - return this; + return writeTrueProperty("ReducedConnectionPadding"); } @SettingsConfig @@ -366,23 +370,20 @@ public TorConfigBuilder runAsDaemonFromSettings() { } public TorConfigBuilder runAsDaemon() { - buffer.append("RunAsDaemon 1").append('\n'); - return this; + return writeTrueProperty("RunAsDaemon"); } public TorConfigBuilder safeSocksDisable() { - buffer.append("SafeSocks 0").append('\n'); - return this; + return writeFalseProperty("SafeSocks"); } public TorConfigBuilder safeSocksEnable() { - buffer.append("SafeSocks 1").append('\n'); - return this; + return writeTrueProperty("SafeSocks"); } @SettingsConfig public TorConfigBuilder safeSocksFromSettings() { - return !settings.hasSafeSocks() ? safeSocksDisable() : safeSocksEnable(); + return settings.hasSafeSocks() ? safeSocksEnable() : this; } public TorConfigBuilder setGeoIpFiles() throws IOException { @@ -413,6 +414,9 @@ public TorConfigBuilder socksPort(String socksPort, String isolationFlag) { @SettingsConfig public TorConfigBuilder socksPortFromSettings() { String socksPort = settings.getSocksPort(); + if (isNullOrEmpty(socksPort)) { + return this; + } if (socksPort.indexOf(':') != -1) { socksPort = socksPort.split(":")[1]; } @@ -425,76 +429,71 @@ public TorConfigBuilder socksPortFromSettings() { } public TorConfigBuilder strictNodesDisable() { - buffer.append("StrictNodes 0\n"); - return this; + return writeFalseProperty("StrictNodes"); } public TorConfigBuilder strictNodesEnable() { - buffer.append("StrictNodes 1\n"); - return this; + return writeTrueProperty("StrictNodes"); } @SettingsConfig public TorConfigBuilder strictNodesFromSettings() { - return settings.hasStrictNodes() ? strictNodesEnable() : strictNodesDisable(); + return settings.hasStrictNodes() ? strictNodesEnable() : this; } public TorConfigBuilder testSocksDisable() { - buffer.append("TestSocks 0").append('\n'); - return this; + return writeFalseProperty("TestSocks"); } public TorConfigBuilder testSocksEnable() { - buffer.append("TestSocks 0").append('\n'); - return this; + return writeTrueProperty("TestSocks"); } @SettingsConfig public TorConfigBuilder testSocksFromSettings() { - return !settings.hasTestSocks() ? testSocksDisable() : this; + return settings.hasTestSocks() ? testSocksEnable() : this; } @SettingsConfig public TorConfigBuilder torrcCustomFromSettings() throws UnsupportedEncodingException { return settings.getCustomTorrc() != null ? - line(new String(settings.getCustomTorrc().getBytes("US-ASCII"))) : this; + writeLine(new String(settings.getCustomTorrc().getBytes("US-ASCII"))) : this; } - public TorConfigBuilder transPort(String transPort) { - if (!isNullOrEmpty(transPort)) - buffer.append("TransPort ").append(transPort).append('\n'); - return this; + public TorConfigBuilder transparentProxyPort(String address, Integer transPort) { + return writeAddress("TransPort", address, transPort, null); } @SettingsConfig public TorConfigBuilder transPortFromSettings() { - return transPort(settings.transPort()); + return transparentProxyPort(settings.getTransparentProxyAddress(), settings.getTransparentProxyPort()); } public TorConfigBuilder transportPluginMeek(String clientPath) { - buffer.append("ClientTransportPlugin meek_lite exec ").append(clientPath).append('\n'); - return this; + return writeLine("ClientTransportPlugin meek_lite exec", clientPath); } public TorConfigBuilder transportPluginObfs(String clientPath) { - buffer.append("ClientTransportPlugin obfs3 exec ").append(clientPath).append('\n'); - buffer.append("ClientTransportPlugin obfs4 exec ").append(clientPath).append('\n'); - return this; + return writeLine("ClientTransportPlugin obfs3 exec", clientPath) + .writeLine("ClientTransportPlugin obfs4 exec", clientPath); } public TorConfigBuilder useBridges() { - buffer.append("UseBridges 1").append('\n'); + writeTrueProperty("UseBridges"); return this; } @SettingsConfig public TorConfigBuilder useBridgesFromSettings() { - return !settings.hasBridges() ? dontUseBridges() : this; + if (settings.hasBridges() && (hasCustomBridges() || hasUserDefinedBridges())) { + useBridges(); + } + return this; } public TorConfigBuilder virtualAddressNetwork(String address) { if (!isNullOrEmpty(address)) - buffer.append("VirtualAddrNetwork ").append(address).append('\n'); + writeLine("VirtualAddrNetwork", address); return this; } @@ -508,70 +507,51 @@ public TorConfigBuilder virtualAddressNetworkFromSettings() { * These entries may be type-specified like: * * - * obfs3 169.229.59.74:31493 AF9F66B7B04F8FF6F32D455F05135250A16543C9 - * - * - * Or it may just be a custom entry like - * - * - * 69.163.45.129:443 9F090DE98CA6F67DEEB1F87EFE7C1BFD884E6E2F + * obfs3 169.229.59.74:31493 AF9F66B7B04F8FF6F32D455F05135250A16543C9 * - * + *

*/ - TorConfigBuilder addBridgesFromResources(String type, int maxBridges) throws IOException { - if(settings.hasBridges()) { - InputStream bridgesStream = context.getInstaller().openBridgesStream(); - int formatType = bridgesStream.read(); - if(formatType == 0) { - addBridges(bridgesStream, type, maxBridges); - } else { - addCustomBridges(bridgesStream); + TorConfigBuilder defaultBridgesFromResources(List userDefinedBridgeTypes) { + if (!settings.hasBridges() || !hasUserDefinedBridges() || hasCustomBridges()) { + return this; + } + ArrayList bridgeTypes = new ArrayList<>(); + for (BridgeType bridgeType : userDefinedBridgeTypes) { + bridgeTypes.add(bridgeType.name().toLowerCase()); + } + InputStream bridgesStream = null; + try { + bridgesStream = context.getInstaller().openDefaultBridgesStream(); + writeDefaultBridgesFromStream(bridgesStream, bridgeTypes); + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (bridgesStream != null) { + try { + bridgesStream.close(); + } catch (IOException e) { + } } } return this; } - /** - * Add bridges from bridges.txt file. - */ - private void addBridges(InputStream input, String bridgeType, int maxBridges) { - if (input == null || isNullOrEmpty(bridgeType) || maxBridges < 1) { - return; - } - boolean hasAddedBridge = false; - List bridges = readBridgesFromStream(input); - Collections.shuffle(bridges, new Random(System.nanoTime())); - int bridgeCount = 0; - for (Bridge b : bridges) { - if (b.type.equals(bridgeType)) { - bridge(b.type, b.config); - hasAddedBridge = true; - if (++bridgeCount > maxBridges) - break; - } - } - if(hasAddedBridge) useBridges(); + private boolean hasCustomBridges() { + return !settings.getCustomBridges().isEmpty(); } /** - * Add custom bridges defined by the user. These will have a bridgeType of 'custom' as the first field. + * Returns true if user has specified bridge types and if bridges.txt file is found. */ - private void addCustomBridges(InputStream input) { - if (input == null) { - return; - } - boolean hasAddedBridge = false; - List bridges = readCustomBridgesFromStream(input); - for (Bridge b : bridges) { - if (b.type.equals("custom")) { - bridgeCustom(b.config); - hasAddedBridge = true; - } - } - if(hasAddedBridge) useBridges(); + private boolean hasUserDefinedBridges() { + return !settings.getBridgeTypes().isEmpty() && context.getInstaller().hasDefaultBridgesStream(); } - private static List readBridgesFromStream(InputStream input) { + /** + * Reads bridges from specified input. If the file doesn't contain any valid bridge entries, + * then this method returns an empty bridge list. + */ + private static List readDefaultBridgesFromStream(InputStream input) { List bridges = new ArrayList<>(); try { BufferedReader br = new BufferedReader(new InputStreamReader(input, "UTF-8")); @@ -589,21 +569,71 @@ private static List readBridgesFromStream(InputStream input) { return bridges; } - private static List readCustomBridgesFromStream(InputStream input) { - List bridges = new ArrayList<>(); - try { - BufferedReader br = new BufferedReader(new InputStreamReader(input, "UTF-8")); - for (String line = br.readLine(); line != null; line = br.readLine()) { - if(line.isEmpty()) { - continue; - } - bridges.add(new Bridge("custom", line)); + TorConfigBuilder writeAddress(String fieldName, String address, Integer port, String flags) { + if (isNullOrEmpty(address) && port == null) { + return this; + } + buffer.append(fieldName).append(" "); + if (!isNullOrEmpty(address)) { + buffer.append(address).append(":"); + } + if (port != null) { + buffer.append(port <= 0 ? "auto" : port); + } else { + buffer.append("auto"); + } + if (!isNullOrEmpty(flags)) { + buffer.append(" ").append(flags); + } + buffer.append('\n'); + return this; + } + + private void writeBridgeTransport(File pluggableTransportClient, BridgeType bridgeType) throws IOException { + switch (bridgeType) { + case MEEK_LITE: + transportPluginMeek(pluggableTransportClient.getCanonicalPath()); + break; + case OBFS3: + case OBFS4: + transportPluginObfs(pluggableTransportClient.getCanonicalPath()); + } + } + + /** + * Writes bridges from bridges.txt file to the config. Only bridges of the specified bridgeTypes will be written. + *

+ * If the input file doesn't contain any valid bridge entries, this method writes nothing to the config. + */ + private void writeDefaultBridgesFromStream(InputStream input, List userDefinedBridgeTypes) { + if (input == null || userDefinedBridgeTypes.isEmpty()) { + return; + } + List bridges = readDefaultBridgesFromStream(input); + for (Bridge b : bridges) { + if (userDefinedBridgeTypes.contains(b.type)) { + bridge(b.type, b.config); } - br.close(); - } catch (Exception e) { - e.printStackTrace(); } - return bridges; + } + + private TorConfigBuilder writeFalseProperty(String name) { + buffer.append(name).append(" 0").append('\n'); + return this; + } + + public TorConfigBuilder writeLine(String value) { + if (!isNullOrEmpty(value)) buffer.append(value).append("\n"); + return this; + } + + public TorConfigBuilder writeLine(String value, String value2) { + return writeLine(value + " " + value2); + } + + private TorConfigBuilder writeTrueProperty(String name) { + buffer.append(name).append(" 1").append('\n'); + return this; } private static class Bridge { diff --git a/universal/src/main/java/com/msopentech/thali/toronionproxy/TorInstaller.java b/universal/src/main/java/com/msopentech/thali/toronionproxy/TorInstaller.java index 308cd837..c97bf3ec 100644 --- a/universal/src/main/java/com/msopentech/thali/toronionproxy/TorInstaller.java +++ b/universal/src/main/java/com/msopentech/thali/toronionproxy/TorInstaller.java @@ -29,20 +29,7 @@ public final InputStream getAssetOrResourceByName(String fileName) { return getClass().getResourceAsStream("/" + fileName); } - /** - * If first byte of stream is 0, then the following stream will have the form - * - * - * ($bridge_type $bridge_info \r\n)* - * - * - * if first byte is 1, the the stream will have the form - * - * ($bridge_info \r\n)* - * - * - * The second form is used for custom bridges from the user. - * - */ - public abstract InputStream openBridgesStream() throws IOException; + public abstract InputStream openDefaultBridgesStream() throws IOException; + + public abstract boolean hasDefaultBridgesStream(); } diff --git a/universal/src/main/java/com/msopentech/thali/toronionproxy/TorSettings.java b/universal/src/main/java/com/msopentech/thali/toronionproxy/TorSettings.java index 2d515d10..eee81fd8 100644 --- a/universal/src/main/java/com/msopentech/thali/toronionproxy/TorSettings.java +++ b/universal/src/main/java/com/msopentech/thali/toronionproxy/TorSettings.java @@ -5,32 +5,35 @@ public interface TorSettings { boolean disableNetwork(); - String dnsPort(); + List getCustomBridges(); String getCustomTorrc(); + String getDnsHost(); + + Integer getDnsPort(); + String getEntryNodes(); String getExcludeNodes(); String getExitNodes(); - int getHttpTunnelPort(); + String getHttpTunnelHost(); + + Integer getHttpTunnelPort(); - /** - * Returns a list of supported bridges. The string value will include the name: meek_lite, obfs4 - */ - List getListOfSupportedBridges(); + List getBridgeTypes(); String getProxyHost(); String getProxyPassword(); - String getProxyPort(); + Integer getProxyPort(); String getProxySocks5Host(); - String getProxySocks5ServerPort(); + Integer getProxySocks5ServerPort(); String getProxyType(); @@ -40,10 +43,14 @@ public interface TorSettings { String getRelayNickname(); - int getRelayPort(); + Integer getRelayPort(); String getSocksPort(); + String getTransparentProxyAddress(); + + Integer getTransparentProxyPort(); + String getVirtualAddressNetwork(); boolean hasBridges(); @@ -74,7 +81,5 @@ public interface TorSettings { boolean runAsDaemon(); - String transPort(); - boolean useSocks5(); } diff --git a/universal/src/test/java/com/msopentech/thali/toronionproxy/TorConfigBuilderTest.java b/universal/src/test/java/com/msopentech/thali/toronionproxy/TorConfigBuilderTest.java new file mode 100644 index 00000000..4be997a3 --- /dev/null +++ b/universal/src/test/java/com/msopentech/thali/toronionproxy/TorConfigBuilderTest.java @@ -0,0 +1,325 @@ +package com.msopentech.thali.toronionproxy; + +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Collections; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +public class TorConfigBuilderTest { + + /** + * Bridges are added in random order + */ + @Test + public void testAddCustomBridges() throws Exception { + TorSettings torSettings = mock(TorSettings.class); + when(torSettings.hasBridges()).thenReturn(true); + when(torSettings.getCustomBridges()).thenReturn(Arrays.asList("b1", "b2")); + + OnionProxyContext context = mock(OnionProxyContext.class); + when(context.getSettings()).thenReturn(torSettings); + + TorConfigBuilder builder = new TorConfigBuilder(context); + builder.customBridgesFromSettings(); + String result = builder.asString(); + assertTrue("Bridge b2\nBridge b1\n".equals(result) || "Bridge b1\nBridge b2\n".equals(result)); + } + + @Test + public void testNoCustomBridgesIfBridgesAreDisabled() throws Exception { + TorSettings torSettings = mock(TorSettings.class); + when(torSettings.hasBridges()).thenReturn(false); + when(torSettings.getCustomBridges()).thenReturn(Arrays.asList("b1", "b2")); + + OnionProxyContext context = mock(OnionProxyContext.class); + when(context.getSettings()).thenReturn(torSettings); + + TorConfigBuilder builder = new TorConfigBuilder(context); + builder.customBridgesFromSettings(); + String result = builder.asString(); + assertTrue(result.isEmpty()); + } + + @Test + public void testNoCustomBridges() throws Exception { + TorSettings torSettings = mock(TorSettings.class); + when(torSettings.hasBridges()).thenReturn(true); + when(torSettings.getCustomBridges()).thenReturn(Collections.emptyList()); + + OnionProxyContext context = mock(OnionProxyContext.class); + when(context.getSettings()).thenReturn(torSettings); + + TorConfigBuilder builder = new TorConfigBuilder(context); + builder.customBridgesFromSettings(); + String result = builder.asString(); + assertTrue(result.isEmpty()); + } + + /** + * Should be empty + */ + @Test + public void testUseBridgesFromSettingsFalse() { + TorSettings torSettings = mock(TorSettings.class); + when(torSettings.hasBridges()).thenReturn(false); + OnionProxyContext context = mock(OnionProxyContext.class); + when(context.getSettings()).thenReturn(torSettings); + TorConfigBuilder builder = new TorConfigBuilder(context); + + builder.useBridgesFromSettings(); + String result = builder.asString(); + assertEquals("", result); + } + + @Test + public void testUseBridgesFromSettingsTrueNoDefaultBridgeStream() { + TorSettings torSettings = mock(TorSettings.class); + when(torSettings.hasBridges()).thenReturn(true); + OnionProxyContext context = mock(OnionProxyContext.class); + when(context.getSettings()).thenReturn(torSettings); + TorConfigBuilder builder = new TorConfigBuilder(context); + + builder.useBridgesFromSettings(); + String result = builder.asString(); + assertEquals("", result); + } + + @Test + public void testUseBridgesFromSettingsNoBridgeStreamAvailable() { + TorSettings torSettings = mock(TorSettings.class); + when(torSettings.hasBridges()).thenReturn(true); + when(torSettings.getBridgeTypes()).thenReturn(Collections.singletonList(BridgeType.OBFS4)); + + OnionProxyContext context = mock(OnionProxyContext.class); + when(context.getSettings()).thenReturn(torSettings); + TorInstaller torInstaller = mock(TorInstaller.class); + when(context.getInstaller()).thenReturn(torInstaller); + when(torInstaller.hasDefaultBridgesStream()).thenReturn(false); + TorConfigBuilder builder = new TorConfigBuilder(context); + + builder.useBridgesFromSettings(); + String result = builder.asString(); + assertEquals("", result); + } + + @Test + public void testUseBridgesFromSettingsWithCustomBridges() { + TorSettings torSettings = mock(TorSettings.class); + when(torSettings.hasBridges()).thenReturn(true); + when(torSettings.getCustomBridges()).thenReturn(Arrays.asList("b1", "b2")); + OnionProxyContext context = mock(OnionProxyContext.class); + when(context.getSettings()).thenReturn(torSettings); + TorConfigBuilder builder = new TorConfigBuilder(context); + + builder.useBridgesFromSettings(); + String result = builder.asString(); + assertEquals("UseBridges 1\n", result); + } + + @Test + public void testUseBridgesFromSettingsWithDefaultBridges() { + TorSettings torSettings = mock(TorSettings.class); + when(torSettings.hasBridges()).thenReturn(true); + when(torSettings.getBridgeTypes()).thenReturn(Collections.singletonList(BridgeType.OBFS4)); + OnionProxyContext context = mock(OnionProxyContext.class); + when(context.getSettings()).thenReturn(torSettings); + + TorInstaller torInstaller = mock(TorInstaller.class); + when(context.getInstaller()).thenReturn(torInstaller); + when(torInstaller.hasDefaultBridgesStream()).thenReturn(true); + TorConfigBuilder builder = new TorConfigBuilder(context); + + builder.useBridgesFromSettings(); + String result = builder.asString(); + assertEquals("UseBridges 1\n", result); + } + + @Test + public void testUseBridgesFromSettingsWithDefaultBridgesButNoBridgesFile() { + TorSettings torSettings = mock(TorSettings.class); + when(torSettings.hasBridges()).thenReturn(true); + when(torSettings.getBridgeTypes()).thenReturn(Collections.singletonList(BridgeType.OBFS4)); + OnionProxyContext context = mock(OnionProxyContext.class); + when(context.getSettings()).thenReturn(torSettings); + + TorInstaller torInstaller = mock(TorInstaller.class); + when(context.getInstaller()).thenReturn(torInstaller); + when(torInstaller.hasDefaultBridgesStream()).thenReturn(false); + TorConfigBuilder builder = new TorConfigBuilder(context); + + builder.useBridgesFromSettings(); + String result = builder.asString(); + assertEquals("", result); + } + + @Test + public void testDefaultBridgesFromSettingsFilterOneType() throws IOException { + String bridgeList = "obfs4 192\nobfs3 190\nobfs4 189\n,meek 170\n"; + InputStream bridgeStream = new ByteArrayInputStream(bridgeList.getBytes()); + + TorSettings torSettings = mock(TorSettings.class); + when(torSettings.hasBridges()).thenReturn(true); + when(torSettings.getBridgeTypes()).thenReturn(Collections.singletonList(BridgeType.OBFS4)); + TorInstaller torInstaller = mock(TorInstaller.class); + when(torInstaller.openDefaultBridgesStream()).thenReturn(bridgeStream); + OnionProxyContext context = mock(OnionProxyContext.class); + when(context.getSettings()).thenReturn(torSettings); + when(context.getInstaller()).thenReturn(torInstaller); + when(torInstaller.hasDefaultBridgesStream()).thenReturn(true); + TorConfigBuilder builder = new TorConfigBuilder(context); + + builder.defaultBridgesFromSettings(); + String result = builder.asString(); + assertTrue("Bridge obfs4 192\nBridge obfs4 189\n".equals(result) + || "Bridge obfs4 189\nBridge obfs4 192\n".equals(result)); + + } + + @Test + public void testDefaultBridgesFromSettingsFilterTwoTypes() throws IOException { + String bridgeList = "obfs4 192\nobfs3 190\nobfs3 189\nmeek_lite 170\n"; + InputStream bridgeStream = new ByteArrayInputStream(bridgeList.getBytes()); + + TorSettings torSettings = mock(TorSettings.class); + when(torSettings.hasBridges()).thenReturn(true); + when(torSettings.getBridgeTypes()).thenReturn(Arrays.asList(BridgeType.OBFS4, BridgeType.MEEK_LITE)); + TorInstaller torInstaller = mock(TorInstaller.class); + when(torInstaller.openDefaultBridgesStream()).thenReturn(bridgeStream); + OnionProxyContext context = mock(OnionProxyContext.class); + when(context.getSettings()).thenReturn(torSettings); + when(context.getInstaller()).thenReturn(torInstaller); + when(torInstaller.hasDefaultBridgesStream()).thenReturn(true); + TorConfigBuilder builder = new TorConfigBuilder(context); + + builder.defaultBridgesFromSettings(); + String result = builder.asString(); + assertTrue("Bridge obfs4 192\nBridge meek_lite 170\n".equals(result) + || "Bridge meek_lite 170\nBridge obfs4 192\n".equals(result)); + + } + + @Test + public void testConfigureTransportsAddedWhenNoBridges() throws IOException { + TorSettings torSettings = mock(TorSettings.class); + when(torSettings.hasBridges()).thenReturn(false); + OnionProxyContext context = mock(OnionProxyContext.class); + when(context.getSettings()).thenReturn(torSettings); + TorConfigBuilder builder = new TorConfigBuilder(context); + File transport = File.createTempFile("transport", ".exe"); + transport.setExecutable(true); + + builder.configurePluggableTransports(transport, Collections.singletonList(BridgeType.OBFS4)); + String result = builder.asString(); + assertTrue(result.contains("ClientTransportPlugin obfs3 exec")); + assertTrue(result.contains("ClientTransportPlugin obfs4 exec")); + + } + + @Test + public void testConfigureTransportsMeek() throws IOException { + TorSettings torSettings = mock(TorSettings.class); + when(torSettings.hasBridges()).thenReturn(false); + OnionProxyContext context = mock(OnionProxyContext.class); + when(context.getSettings()).thenReturn(torSettings); + TorConfigBuilder builder = new TorConfigBuilder(context); + File transport = File.createTempFile("transport", ".exe"); + transport.setExecutable(true); + + builder.configurePluggableTransports(transport, Arrays.asList(BridgeType.MEEK_LITE)); + String result = builder.asString(); + assertTrue(result.contains("ClientTransportPlugin meek_lite exec")); + assertFalse(result.contains("ClientTransportPlugin obfs4 exec")); + } + + @Test + public void testHttpTunnelPort() throws IOException { + TorSettings torSettings = mock(TorSettings.class); + when(torSettings.getHttpTunnelHost()).thenReturn("192.1.1.1"); + when(torSettings.getHttpTunnelPort()).thenReturn(8080); + when(torSettings.hasIsolationAddressFlagForTunnel()).thenReturn(true); + + OnionProxyContext context = mock(OnionProxyContext.class); + when(context.getSettings()).thenReturn(torSettings); + TorConfigBuilder builder = new TorConfigBuilder(context); + builder.httpTunnelPortFromSettings(); + String result = builder.asString(); + assertEquals("HTTPTunnelPort 192.1.1.1:8080 IsolateDestAddr\n", result); + } + + @Test + public void testTransparentProxy() throws IOException { + TorSettings torSettings = mock(TorSettings.class); + when(torSettings.getTransparentProxyAddress()).thenReturn("192.1.1.1"); + when(torSettings.getTransparentProxyPort()).thenReturn(8080); + OnionProxyContext context = mock(OnionProxyContext.class); + when(context.getSettings()).thenReturn(torSettings); + TorConfigBuilder builder = new TorConfigBuilder(context); + builder.transPortFromSettings(); + String result = builder.asString(); + assertEquals("TransPort 192.1.1.1:8080\n", result); + } + + @Test + public void testDnsPort() throws IOException { + TorSettings torSettings = mock(TorSettings.class); + when(torSettings.getDnsPort()).thenReturn(5111); + OnionProxyContext context = mock(OnionProxyContext.class); + when(context.getSettings()).thenReturn(torSettings); + TorConfigBuilder builder = new TorConfigBuilder(context); + builder.dnsPortFromSettings(); + String result = builder.asString(); + assertEquals("DNSPort 5111\n", result); + } + + @Test + public void testAddAddress() throws IOException { + OnionProxyContext context = mock(OnionProxyContext.class); + TorConfigBuilder builder = new TorConfigBuilder(context); + builder.writeAddress("fieldName", "192.1.1.0", 8080, null); + String result = builder.asString(); + assertEquals("fieldName 192.1.1.0:8080\n", result); + } + + @Test + public void testAddAddressNoAddress() throws IOException { + OnionProxyContext context = mock(OnionProxyContext.class); + TorConfigBuilder builder = new TorConfigBuilder(context); + builder.writeAddress("fieldName", null, 8080, null); + String result = builder.asString(); + assertEquals("fieldName 8080\n", result); + } + + @Test + public void testAddAddressNoPort() throws IOException { + OnionProxyContext context = mock(OnionProxyContext.class); + TorConfigBuilder builder = new TorConfigBuilder(context); + builder.writeAddress("fieldName", "192.1.1.0", null, null); + String result = builder.asString(); + assertEquals("fieldName 192.1.1.0:auto\n", result); + } + + @Test + public void testAddAddressIllegalPort() throws IOException { + OnionProxyContext context = mock(OnionProxyContext.class); + TorConfigBuilder builder = new TorConfigBuilder(context); + builder.writeAddress("fieldName", "192.1.1.0", 0, null); + String result = builder.asString(); + assertEquals("fieldName 192.1.1.0:auto\n", result); + } + + @Test + public void testAddAddressNull() throws IOException { + OnionProxyContext context = mock(OnionProxyContext.class); + TorConfigBuilder builder = new TorConfigBuilder(context); + builder.writeAddress("fieldName", null, null, null); + String result = builder.asString(); + assertEquals("", result); + } +} diff --git a/universal/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/universal/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 00000000..ca6ee9ce --- /dev/null +++ b/universal/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline \ No newline at end of file