From 74842cba92591aa9fbf64e8c6f39900a68b0c11c Mon Sep 17 00:00:00 2001 From: cyBerta Date: Fri, 19 Oct 2018 23:15:13 +0200 Subject: #8919 update dependencies --- .../main/java/de/blinkt/openvpn/core/CIDRIP.java | 8 +- .../java/de/blinkt/openvpn/core/ConfigParser.java | 379 ++++++++++++--------- .../java/de/blinkt/openvpn/core/Connection.java | 33 +- .../java/de/blinkt/openvpn/core/ExtAuthHelper.java | 259 ++++++++++++++ .../blinkt/openvpn/core/ICSOpenVPNApplication.java | 2 +- .../de/blinkt/openvpn/core/LogFileHandler.java | 4 +- .../java/de/blinkt/openvpn/core/NativeUtils.java | 22 +- .../java/de/blinkt/openvpn/core/NetworkSpace.java | 103 +++--- .../java/de/blinkt/openvpn/core/NetworkUtils.java | 75 ++++ .../de/blinkt/openvpn/core/OpenVPNService.java | 181 +++++++--- .../openvpn/core/OpenVpnManagementThread.java | 239 +++++++++---- .../java/de/blinkt/openvpn/core/OrbotHelper.java | 202 +++++++++++ .../de/blinkt/openvpn/core/ProfileManager.java | 13 +- .../de/blinkt/openvpn/core/VPNLaunchHelper.java | 2 +- .../java/de/blinkt/openvpn/core/VpnStatus.java | 6 +- 15 files changed, 1194 insertions(+), 334 deletions(-) create mode 100644 app/src/main/java/de/blinkt/openvpn/core/ExtAuthHelper.java create mode 100644 app/src/main/java/de/blinkt/openvpn/core/NetworkUtils.java create mode 100644 app/src/main/java/de/blinkt/openvpn/core/OrbotHelper.java (limited to 'app/src/main/java/de/blinkt/openvpn/core') diff --git a/app/src/main/java/de/blinkt/openvpn/core/CIDRIP.java b/app/src/main/java/de/blinkt/openvpn/core/CIDRIP.java index 799c68c9..ca3d1161 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/CIDRIP.java +++ b/app/src/main/java/de/blinkt/openvpn/core/CIDRIP.java @@ -14,6 +14,11 @@ class CIDRIP { public CIDRIP(String ip, String mask) { mIp = ip; + len = calculateLenFromMask(mask); + + } + + public static int calculateLenFromMask(String mask) { long netmask = getInt(mask); // Add 33. bit to ensure the loop terminates @@ -24,6 +29,7 @@ class CIDRIP { lenZeros++; netmask = netmask >> 1; } + int len; // Check if rest of netmask is only 1s if (netmask != (0x1ffffffffl >> lenZeros)) { // Asume no CIDR, set /32 @@ -31,7 +37,7 @@ class CIDRIP { } else { len = 32 - lenZeros; } - + return len; } public CIDRIP(String address, int prefix_length) { diff --git a/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java b/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java index 9889754d..0148bfb7 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java +++ b/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java @@ -5,17 +5,15 @@ package de.blinkt.openvpn.core; -import android.text.TextUtils; +import android.os.Build; import android.support.v4.util.Pair; +import android.text.TextUtils; import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; import java.io.StringReader; -import java.util.Collections; -import java.util.HashMap; -import java.util.Locale; -import java.util.Vector; +import java.util.*; import de.blinkt.openvpn.VpnProfile; @@ -29,10 +27,127 @@ public class ConfigParser { public static final String CONVERTED_PROFILE = "converted Profile"; - private HashMap>> options = new HashMap>>(); + final String[] unsupportedOptions = {"config", + "tls-server" + + }; + // Ignore all scripts + // in most cases these won't work and user who wish to execute scripts will + // figure out themselves + private final String[] ignoreOptions = {"tls-client", + "allow-recursive-routing", + "askpass", + "auth-nocache", + "up", + "down", + "route-up", + "ipchange", + "route-pre-down", + "auth-user-pass-verify", + "block-outside-dns", + "client-cert-not-required", + "dhcp-release", + "dhcp-renew", + "dh", + "group", + "ip-win32", + "ifconfig-nowarn", + "management-hold", + "management", + "management-client", + "management-query-remote", + "management-query-passwords", + "management-query-proxy", + "management-external-key", + "management-forget-disconnect", + "management-signal", + "management-log-cache", + "management-up-down", + "management-client-user", + "management-client-group", + "pause-exit", + "preresolve", + "plugin", + "machine-readable-output", + "persist-key", + "push", + "register-dns", + "route-delay", + "route-gateway", + "route-metric", + "route-method", + "status", + "script-security", + "show-net-up", + "suppress-timestamps", + "tap-sleep", + "tmp-dir", + "tun-ipv6", + "topology", + "user", + "win-sys", + }; + private final String[][] ignoreOptionsWithArg = + { + {"setenv", "IV_GUI_VER"}, + {"setenv", "IV_PLAT_VER"}, + {"setenv", "IV_OPENVPN_GUI_VERSION"}, + {"engine", "dynamic"}, + {"setenv", "CLIENT_CERT"}, + {"resolv-retry", "60"} + }; + private final String[] connectionOptions = { + "local", + "remote", + "float", + "port", + "connect-retry", + "connect-timeout", + "connect-retry-max", + "link-mtu", + "tun-mtu", + "tun-mtu-extra", + "fragment", + "mtu-disc", + "local-port", + "remote-port", + "bind", + "nobind", + "proto", + "http-proxy", + "http-proxy-retry", + "http-proxy-timeout", + "http-proxy-option", + "socks-proxy", + "socks-proxy-retry", + "http-proxy-user-pass", + "explicit-exit-notify", + }; + private HashSet connectionOptionsSet = new HashSet<>(Arrays.asList(connectionOptions)); + + private HashMap>> options = new HashMap<>(); private HashMap> meta = new HashMap>(); private String auth_user_pass_file; + static public void useEmbbedUserAuth(VpnProfile np, String inlinedata) { + String data = VpnProfile.getEmbeddedContent(inlinedata); + String[] parts = data.split("\n"); + if (parts.length >= 2) { + np.mUsername = parts[0]; + np.mPassword = parts[1]; + } + } + + static public void useEmbbedHttpAuth(Connection c, String inlinedata) { + String data = VpnProfile.getEmbeddedContent(inlinedata); + String[] parts = data.split("\n"); + if (parts.length >= 2) { + c.mProxyAuthUser = parts[0]; + c.mProxyAuthPassword = parts[1]; + c.mUseProxyAuth = true; + } + } + public void parseConfig(Reader reader) throws IOException, ConfigParseError { HashMap optionAliases = new HashMap<>(); @@ -76,7 +191,7 @@ public class ConfigParser { checkinlinefile(args, br); String optionname = args.get(0); - if (optionAliases.get(optionname)!=null) + if (optionAliases.get(optionname) != null) optionname = optionAliases.get(optionname); if (!options.containsKey(optionname)) { @@ -119,8 +234,8 @@ public class ConfigParser { } } while (true); - if(inlinefile.endsWith("\n")) - inlinefile = inlinefile.substring(0, inlinefile.length()-1); + if (inlinefile.endsWith("\n")) + inlinefile = inlinefile.substring(0, inlinefile.length() - 1); args.clear(); args.add(argname); @@ -133,11 +248,6 @@ public class ConfigParser { return auth_user_pass_file; } - enum linestate { - initial, - readin_single_quote, reading_quoted, reading_unquoted, done - } - private boolean space(char c) { // I really hope nobody is using zero bytes inside his/her config file // to sperate parameter but here we go: @@ -145,15 +255,6 @@ public class ConfigParser { } - public static class ConfigParseError extends Exception { - private static final long serialVersionUID = -60L; - - public ConfigParseError(String msg) { - super(msg); - } - } - - // adapted openvpn's parse function to java private Vector parseline(String line) throws ConfigParseError { Vector parameters = new Vector(); @@ -226,7 +327,7 @@ public class ConfigParser { backslash = false; } - /* store parameter character */ + /* store parameter character */ if (out != 0) { currentarg += out; } @@ -235,105 +336,6 @@ public class ConfigParser { return parameters; } - - final String[] unsupportedOptions = {"config", - "tls-server" - - }; - - // Ignore all scripts - // in most cases these won't work and user who wish to execute scripts will - // figure out themselves - final String[] ignoreOptions = {"tls-client", - "askpass", - "auth-nocache", - "up", - "down", - "route-up", - "ipchange", - "route-up", - "route-pre-down", - "auth-user-pass-verify", - "block-outside-dns", - "dhcp-release", - "dhcp-renew", - "dh", - "group", - "allow-recursive-routing", - "ip-win32", - "ifconfig-nowarn", - "management-hold", - "management", - "management-client", - "management-query-remote", - "management-query-passwords", - "management-query-proxy", - "management-external-key", - "management-forget-disconnect", - "management-signal", - "management-log-cache", - "management-up-down", - "management-client-user", - "management-client-group", - "pause-exit", - "preresolve", - "plugin", - "machine-readable-output", - "persist-key", - "push", - "register-dns", - "route-delay", - "route-gateway", - "route-metric", - "route-method", - "status", - "script-security", - "show-net-up", - "suppress-timestamps", - "tmp-dir", - "tun-ipv6", - "topology", - "user", - "win-sys", - }; - - final String[][] ignoreOptionsWithArg = - { - {"setenv", "IV_GUI_VER"}, - {"setenv", "IV_OPENVPN_GUI_VERSION"}, - {"engine", "dynamic"}, - {"setenv", "CLIENT_CERT"}, - {"resolve-retry","60"} - }; - - final String[] connectionOptions = { - "local", - "remote", - "float", - "port", - "connect-retry", - "connect-timeout", - "connect-retry-max", - "link-mtu", - "tun-mtu", - "tun-mtu-extra", - "fragment", - "mtu-disc", - "local-port", - "remote-port", - "bind", - "nobind", - "proto", - "http-proxy", - "http-proxy-retry", - "http-proxy-timeout", - "http-proxy-option", - "socks-proxy", - "socks-proxy-retry", - "explicit-exit-notify", - }; - - // This method is far too long @SuppressWarnings("ConstantConditions") public VpnProfile convertProfile() throws ConfigParseError, IOException { @@ -403,8 +405,8 @@ public class ConfigParser { } Vector routeNoPull = getOption("route-nopull", 0, 0); - if (routeNoPull!=null) - np.mRoutenopull=true; + if (routeNoPull != null) + np.mRoutenopull = true; // Also recognize tls-auth [inline] direction ... Vector> tlsauthoptions = getAllOption("tls-auth", 1, 2); @@ -426,7 +428,7 @@ public class ConfigParser { np.mTLSAuthDirection = direction.get(1); Vector tlscrypt = getOption("tls-crypt", 1, 1); - if (tlscrypt!=null) { + if (tlscrypt != null) { np.mUseTLSAuth = true; np.mTLSAuthFilename = tlscrypt.get(1); np.mTLSAuthDirection = "tls-crypt"; @@ -467,7 +469,7 @@ public class ConfigParser { } - Vector tunmtu = getOption("mtu", 1, 1); + Vector tunmtu = getOption("tun-mtu", 1, 1); if (tunmtu != null) { try { @@ -478,7 +480,6 @@ public class ConfigParser { } - Vector mode = getOption("mode", 1, 1); if (mode != null) { if (!mode.get(1).equals("p2p")) @@ -592,9 +593,9 @@ public class ConfigParser { } - Vector x509usernamefield = getOption("x509-username-field", 1,1); - if (x509usernamefield!=null) { - np.mx509UsernameField = x509usernamefield.get(1); + Vector x509usernamefield = getOption("x509-username-field", 1, 1); + if (x509usernamefield != null) { + np.mx509UsernameField = x509usernamefield.get(1); } @@ -666,7 +667,7 @@ public class ConfigParser { if (crlfile != null) { // If the 'dir' parameter is present just add it as custom option .. if (crlfile.size() == 3 && crlfile.get(2).equals("dir")) - np.mCustomConfigOptions += TextUtils.join(" ", crlfile) + "\n"; + np.mCustomConfigOptions += join(" ", crlfile) + "\n"; else // Save the filename for the config converter to add later np.mCrlFilename = crlfile.get(1); @@ -732,6 +733,13 @@ public class ConfigParser { return np; } + private String join(String s, Vector str) { + if (Build.VERSION.SDK_INT > 26) + return String.join(s, str); + else + return TextUtils.join(s, str); + } + private Pair parseConnection(String connection, Connection defaultValues) throws IOException, ConfigParseError { // Parse a connection Block as a new configuration file @@ -783,20 +791,48 @@ public class ConfigParser { } } - // Parse remote config - Vector> remotes = getAllOption("remote", 1, 3); + Vector proxy = getOption("socks-proxy", 1, 2); + if (proxy == null) + proxy = getOption("http-proxy", 2, 2); + + if (proxy != null) { + if (proxy.get(0).equals("socks-proxy")) { + conn.mProxyType = Connection.ProxyType.SOCKS5; + // socks defaults to 1080, http always sets port + conn.mProxyPort = "1080"; + } else { + conn.mProxyType = Connection.ProxyType.HTTP; + } + + conn.mProxyName = proxy.get(1); + if (proxy.size() >= 3) + conn.mProxyPort = proxy.get(2); + } + Vector httpproxyauthhttp = getOption("http-proxy-user-pass", 1, 1); + if (httpproxyauthhttp != null) + useEmbbedHttpAuth(conn, httpproxyauthhttp.get(1)); - // Assume that we need custom options if connectionDefault are set - if (connDefault != null) { - for (Vector> option : options.values()) { - conn.mCustomConfiguration += getOptionStrings(option); + // Parse remote config + Vector> remotes = getAllOption("remote", 1, 3); + + + Vector optionsToRemove = new Vector<>(); + // Assume that we need custom options if connectionDefault are set or in the connection specific set + for (Map.Entry>> option : options.entrySet()) { + if (connDefault != null || connectionOptionsSet.contains(option.getKey())) { + conn.mCustomConfiguration += getOptionStrings(option.getValue()); + optionsToRemove.add(option.getKey()); } - if (!TextUtils.isEmpty(conn.mCustomConfiguration)) - conn.mUseCustomConfig = true; } + for (String o: optionsToRemove) + options.remove(o); + + if (!(conn.mCustomConfiguration == null || "".equals(conn.mCustomConfiguration.trim()))) + conn.mUseCustomConfig = true; + // Make remotes empty to simplify code if (remotes == null) remotes = new Vector>(); @@ -821,6 +857,7 @@ public class ConfigParser { } i++; } + return Pair.create(conn, connections); } @@ -830,19 +867,19 @@ public class ConfigParser { boolean noIpv4 = false; if (defaultRoute) - for (Vector redirect : defgw) - for (int i = 1; i < redirect.size(); i++) { - if (redirect.get(i).equals("block-local")) - np.mAllowLocalLAN = false; - else if (redirect.get(i).equals("unblock-local")) - np.mAllowLocalLAN = true; - else if (redirect.get(i).equals("!ipv4")) - noIpv4=true; - else if (redirect.get(i).equals("ipv6")) - np.mUseDefaultRoutev6=true; - } + for (Vector redirect : defgw) + for (int i = 1; i < redirect.size(); i++) { + if (redirect.get(i).equals("block-local")) + np.mAllowLocalLAN = false; + else if (redirect.get(i).equals("unblock-local")) + np.mAllowLocalLAN = true; + else if (redirect.get(i).equals("!ipv4")) + noIpv4 = true; + else if (redirect.get(i).equals("ipv6")) + np.mUseDefaultRoutev6 = true; + } if (defaultRoute && !noIpv4) - np.mUseDefaultRoute=true; + np.mUseDefaultRoute = true; } private boolean isUdpProto(String proto) throws ConfigParseError { @@ -861,15 +898,6 @@ public class ConfigParser { return isudp; } - static public void useEmbbedUserAuth(VpnProfile np, String inlinedata) { - String data = VpnProfile.getEmbeddedContent(inlinedata); - String[] parts = data.split("\n"); - if (parts.length >= 2) { - np.mUsername = parts[0]; - np.mPassword = parts[1]; - } - } - private void checkIgnoreAndInvalidOptions(VpnProfile np) throws ConfigParseError { for (String option : unsupportedOptions) if (options.containsKey(option)) @@ -880,7 +908,16 @@ public class ConfigParser { options.remove(option); - if (options.size() > 0) { + boolean customOptions=false; + for (Vector> option: options.values()) + { + for (Vector optionsline : option) { + if (!ignoreThisOption(optionsline)) { + customOptions = true; + } + } + } + if (customOptions) { np.mCustomConfigOptions = "# These options found in the config file do not map to config settings:\n" + np.mCustomConfigOptions; @@ -894,7 +931,6 @@ public class ConfigParser { } } - boolean ignoreThisOption(Vector option) { for (String[] ignoreOption : ignoreOptionsWithArg) { @@ -920,7 +956,7 @@ public class ConfigParser { if (!ignoreThisOption(optionsline)) { // Check if option had been inlined and inline again if (optionsline.size() == 2 && - ("extra-certs".equals(optionsline.get(0)) || "http-proxy-user-pass".equals(optionsline.get(0)))) { + "extra-certs".equals(optionsline.get(0))) { custom += VpnProfile.insertFileData(optionsline.get(0), optionsline.get(1)); } else { for (String arg : optionsline) @@ -932,7 +968,6 @@ public class ConfigParser { return custom; } - private void fixup(VpnProfile np) { if (np.mRemoteCN.equals(np.mServerName)) { np.mRemoteCN = ""; @@ -947,7 +982,6 @@ public class ConfigParser { return alloptions.lastElement(); } - private Vector> getAllOption(String option, int minarg, int maxarg) throws ConfigParseError { Vector> args = options.get(option); if (args == null) @@ -964,6 +998,19 @@ public class ConfigParser { return args; } + enum linestate { + initial, + readin_single_quote, reading_quoted, reading_unquoted, done + } + + public static class ConfigParseError extends Exception { + private static final long serialVersionUID = -60L; + + public ConfigParseError(String msg) { + super(msg); + } + } + } diff --git a/app/src/main/java/de/blinkt/openvpn/core/Connection.java b/app/src/main/java/de/blinkt/openvpn/core/Connection.java index ff15daec..df071894 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/Connection.java +++ b/app/src/main/java/de/blinkt/openvpn/core/Connection.java @@ -19,11 +19,25 @@ public class Connection implements Serializable, Cloneable { public boolean mEnabled = true; public int mConnectTimeout = 0; public static final int CONNECTION_DEFAULT_TIMEOUT = 120; + public ProxyType mProxyType = ProxyType.NONE; + public String mProxyName = "proxy.example.com"; + public String mProxyPort = "8080"; + + public boolean mUseProxyAuth; + public String mProxyAuthUser = null; + public String mProxyAuthPassword = null; + + public enum ProxyType { + NONE, + HTTP, + SOCKS5, + ORBOT + } private static final long serialVersionUID = 92031902903829089L; - public String getConnectionBlock() { + public String getConnectionBlock(boolean isOpenVPN3) { String cfg = ""; // Server Address @@ -39,14 +53,31 @@ public class Connection implements Serializable, Cloneable { if (mConnectTimeout != 0) cfg += String.format(Locale.US, " connect-timeout %d\n", mConnectTimeout); + // OpenVPN 2.x manages proxy connection via management interface + if ((isOpenVPN3 || usesExtraProxyOptions()) && mProxyType == ProxyType.HTTP) + { + cfg+=String.format(Locale.US,"http-proxy %s %s\n", mProxyName, mProxyPort); + if (mUseProxyAuth) + cfg+=String.format(Locale.US, "\n%s\n%s\n\n", mProxyAuthUser, mProxyAuthPassword); + } + if (usesExtraProxyOptions() && mProxyType == ProxyType.SOCKS5) { + cfg+=String.format(Locale.US,"socks-proxy %s %s\n", mProxyName, mProxyPort); + } if (!TextUtils.isEmpty(mCustomConfiguration) && mUseCustomConfig) { cfg += mCustomConfiguration; cfg += "\n"; } + + return cfg; } + public boolean usesExtraProxyOptions() { + return (mUseCustomConfig && mCustomConfiguration.contains("http-proxy-option ")); + } + + @Override public Connection clone() throws CloneNotSupportedException { return (Connection) super.clone(); diff --git a/app/src/main/java/de/blinkt/openvpn/core/ExtAuthHelper.java b/app/src/main/java/de/blinkt/openvpn/core/ExtAuthHelper.java new file mode 100644 index 00000000..166bce12 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/core/ExtAuthHelper.java @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2012-2018 Arne Schwabe + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt + */ + +package de.blinkt.openvpn.core; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.*; +import android.security.KeyChainException; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.WorkerThread; +import android.widget.ArrayAdapter; +import android.widget.Spinner; +import android.widget.SpinnerAdapter; +import de.blinkt.openvpn.api.ExternalCertificateProvider; + +import java.io.ByteArrayInputStream; +import java.io.Closeable; +import java.io.UnsupportedEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.*; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +public class ExtAuthHelper { + + public static final String ACTION_CERT_CONFIGURATION = "de.blinkt.openvpn.api.ExternalCertificateConfiguration"; + public static final String ACTION_CERT_PROVIDER = "de.blinkt.openvpn.api.ExternalCertificateProvider"; + + public static final String EXTRA_ALIAS = "de.blinkt.openvpn.api.KEY_ALIAS"; + public static final String EXTRA_DESCRIPTION = "de.blinkt.openvpn.api.KEY_DESCRIPTION"; + + + public static void setExternalAuthProviderSpinnerList(Spinner spinner, String selectedApp) { + Context c = spinner.getContext(); + final PackageManager pm = c.getPackageManager(); + ArrayList extProviders = getExternalAuthProviderList(c); + + int selectedPos = -1; + + if (extProviders.size() ==0) + { + selectedApp = ""; + ExternalAuthProvider noauthprovider = new ExternalAuthProvider(); + noauthprovider.label = "No external auth provider found"; + noauthprovider.packageName = selectedApp; + noauthprovider.configurable = false; + extProviders.add(noauthprovider); + } + + + for (int i = 0; i < extProviders.size(); i++) { + if (extProviders.get(i).packageName.equals(selectedApp)) + selectedPos = i; + } + SpinnerAdapter extAppAdapter = new ArrayAdapter(c, android.R.layout.simple_spinner_item, android.R.id.text1, extProviders); + spinner.setAdapter(extAppAdapter); + if (selectedPos != -1) + spinner.setSelection(selectedPos); + } + + static ArrayList getExternalAuthProviderList(Context c) { + Intent configureExtAuth = new Intent(ACTION_CERT_CONFIGURATION); + + final PackageManager packageManager = c.getPackageManager(); + List configureList = + packageManager.queryIntentActivities(configureExtAuth, 0); + + Intent serviceExtAuth = new Intent(ACTION_CERT_PROVIDER); + + List serviceList = + packageManager.queryIntentServices(serviceExtAuth, 0); + + + // For now only list those who appear in both lists + + ArrayList providers = new ArrayList(); + + for (ResolveInfo service : serviceList) { + ExternalAuthProvider ext = new ExternalAuthProvider(); + ext.packageName = service.serviceInfo.packageName; + + ext.label = (String) service.serviceInfo.applicationInfo.loadLabel(packageManager); + + for (ResolveInfo activity : configureList) { + if (service.serviceInfo.packageName.equals(activity.activityInfo.packageName)) { + ext.configurable = true; + } + } + providers.add(ext); + + } + return providers; + + } + + @Nullable + @WorkerThread + public static byte[] signData(@NonNull Context context, + @NonNull String extAuthPackageName, + @NonNull String alias, + @NonNull byte[] data + ) throws KeyChainException, InterruptedException + + { + + + try (ExternalAuthProviderConnection authProviderConnection = bindToExtAuthProvider(context.getApplicationContext(), extAuthPackageName)) { + ExternalCertificateProvider externalAuthProvider = authProviderConnection.getService(); + return externalAuthProvider.getSignedData(alias, data); + + } catch (RemoteException e) { + throw new KeyChainException(e); + } + } + + @Nullable + @WorkerThread + public static X509Certificate[] getCertificateChain(@NonNull Context context, + @NonNull String extAuthPackageName, + @NonNull String alias) throws KeyChainException { + + final byte[] certificateBytes; + try (ExternalAuthProviderConnection authProviderConnection = bindToExtAuthProvider(context.getApplicationContext(), extAuthPackageName)) { + ExternalCertificateProvider externalAuthProvider = authProviderConnection.getService(); + certificateBytes = externalAuthProvider.getCertificateChain(alias); + if (certificateBytes == null) { + return null; + } + Collection chain = toCertificates(certificateBytes); + return chain.toArray(new X509Certificate[chain.size()]); + + } catch (RemoteException | RuntimeException | InterruptedException e) { + throw new KeyChainException(e); + } + } + + public static Bundle getCertificateMetaData(@NonNull Context context, + @NonNull String extAuthPackageName, + String alias) throws KeyChainException + { + try (ExternalAuthProviderConnection authProviderConnection = bindToExtAuthProvider(context.getApplicationContext(), extAuthPackageName)) { + ExternalCertificateProvider externalAuthProvider = authProviderConnection.getService(); + return externalAuthProvider.getCertificateMetaData(alias); + + } catch (RemoteException | RuntimeException | InterruptedException e) { + throw new KeyChainException(e); + } + } + + public static Collection toCertificates(@NonNull byte[] bytes) { + final String BEGINCERT = "-----BEGIN CERTIFICATE-----"; + try { + Vector retCerts = new Vector<>(); + // Java library is broken, although the javadoc says it will extract all certificates from a byte array + // it only extracts the first one + String allcerts = new String(bytes, "iso8859-1"); + String[] certstrings = allcerts.split(BEGINCERT); + for (String certstring: certstrings) { + certstring = BEGINCERT + certstring; + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + retCerts.addAll((Collection) certFactory.generateCertificates( + new ByteArrayInputStream((certstring.getBytes("iso8859-1"))))); + + } + return retCerts; + + } catch (CertificateException e) { + throw new AssertionError(e); + } catch (UnsupportedEncodingException e) { + throw new AssertionError(e); + } + } + + // adapted form Keychain + @WorkerThread + public static ExternalAuthProviderConnection bindToExtAuthProvider(@NonNull Context context, String packagename) throws KeyChainException, InterruptedException { + ensureNotOnMainThread(context); + final BlockingQueue q = new LinkedBlockingQueue<>(1); + ServiceConnection extAuthServiceConnection = new ServiceConnection() { + volatile boolean mConnectedAtLeastOnce = false; + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + if (!mConnectedAtLeastOnce) { + mConnectedAtLeastOnce = true; + try { + q.put(ExternalCertificateProvider.Stub.asInterface(service)); + } catch (InterruptedException e) { + // will never happen, since the queue starts with one available slot + } + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + } + }; + Intent intent = new Intent(ACTION_CERT_PROVIDER); + intent.setPackage(packagename); + + if (!context.bindService(intent, extAuthServiceConnection, Context.BIND_AUTO_CREATE)) { + throw new KeyChainException("could not bind to external authticator app: " + packagename); + } + return new ExternalAuthProviderConnection(context, extAuthServiceConnection, q.take()); + } + + private static void ensureNotOnMainThread(@NonNull Context context) { + Looper looper = Looper.myLooper(); + if (looper != null && looper == context.getMainLooper()) { + throw new IllegalStateException( + "calling this from your main thread can lead to deadlock"); + } + } + + public static class ExternalAuthProvider { + + public String packageName; + public boolean configurable = false; + private String label; + + @Override + public String toString() { + return label; + } + } + + public static class ExternalAuthProviderConnection implements Closeable { + private final Context context; + private final ServiceConnection serviceConnection; + private final ExternalCertificateProvider service; + + protected ExternalAuthProviderConnection(Context context, + ServiceConnection serviceConnection, + ExternalCertificateProvider service) { + this.context = context; + this.serviceConnection = serviceConnection; + this.service = service; + } + + @Override + public void close() { + context.unbindService(serviceConnection); + } + + public ExternalCertificateProvider getService() { + return service; + } + } +} diff --git a/app/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java b/app/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java index 38f51807..5b1307b7 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java +++ b/app/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java @@ -65,4 +65,4 @@ public class ICSOpenVPNApplication extends Application { mChannel.setLightColor(Color.BLUE); mNotificationManager.createNotificationChannel(mChannel); } -} +} \ No newline at end of file diff --git a/app/src/main/java/de/blinkt/openvpn/core/LogFileHandler.java b/app/src/main/java/de/blinkt/openvpn/core/LogFileHandler.java index 57d1fb22..fca633b6 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/LogFileHandler.java +++ b/app/src/main/java/de/blinkt/openvpn/core/LogFileHandler.java @@ -134,7 +134,9 @@ class LogFileHandler extends Handler { if (!logfile.exists() || !logfile.canRead()) return; - readCacheContents(new FileInputStream(logfile)); + FileInputStream log = new FileInputStream(logfile); + readCacheContents(log); + log.close(); } catch (java.io.IOException | java.lang.RuntimeException e) { VpnStatus.logError("Reading cached logfile failed"); diff --git a/app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java b/app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java index 70c7455a..6b633c34 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java +++ b/app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java @@ -16,7 +16,15 @@ public class NativeUtils { static native void jniclose(int fdint); - public static native String getNativeAPI(); + public static String getNativeAPI() + { + if (isRoboUnitTest()) + return "ROBO"; + else + return getJNIAPI(); + } + + private static native String getJNIAPI(); public final static int[] openSSLlengths = { @@ -26,8 +34,14 @@ public class NativeUtils { public static native double[] getOpenSSLSpeed(String algorithm, int testnum); static { - System.loadLibrary("opvpnutil"); - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) - System.loadLibrary("jbcrypto"); + if (!isRoboUnitTest()) { + System.loadLibrary("opvpnutil"); + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) + System.loadLibrary("jbcrypto"); + } + } + + public static boolean isRoboUnitTest() { + return "robolectric".equals(Build.FINGERPRINT); } } diff --git a/app/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java b/app/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java index 37689b3f..0c54b050 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java +++ b/app/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java @@ -7,9 +7,6 @@ package de.blinkt.openvpn.core; import android.os.Build; import android.support.annotation.NonNull; -import android.text.TextUtils; - -import junit.framework.Assert; import java.math.BigInteger; import java.net.Inet6Address; @@ -21,9 +18,17 @@ import java.util.Vector; import se.leap.bitmaskclient.BuildConfig; + + public class NetworkSpace { - static class ipAddress implements Comparable { + static void assertTrue(boolean f) + { + if (!f) + throw new IllegalStateException(); + } + + static class IpAddress implements Comparable { private BigInteger netAddress; public int networkMask; private boolean included; @@ -38,7 +43,7 @@ public class NetworkSpace { * 2. smaller networks are returned as smaller */ @Override - public int compareTo(@NonNull ipAddress another) { + public int compareTo(@NonNull IpAddress another) { int comp = getFirstAddress().compareTo(another.getFirstAddress()); if (comp != 0) return comp; @@ -59,22 +64,22 @@ public class NetworkSpace { */ @Override public boolean equals(Object o) { - if (!(o instanceof ipAddress)) + if (!(o instanceof IpAddress)) return super.equals(o); - ipAddress on = (ipAddress) o; + IpAddress on = (IpAddress) o; return (networkMask == on.networkMask) && on.getFirstAddress().equals(getFirstAddress()); } - public ipAddress(CIDRIP ip, boolean include) { + public IpAddress(CIDRIP ip, boolean include) { included = include; netAddress = BigInteger.valueOf(ip.getInt()); networkMask = ip.len; isV4 = true; } - public ipAddress(Inet6Address address, int mask, boolean include) { + public IpAddress(Inet6Address address, int mask, boolean include) { networkMask = mask; included = include; @@ -130,7 +135,7 @@ public class NetworkSpace { return String.format(Locale.US, "%s/%d", getIPv6Address(), networkMask); } - ipAddress(BigInteger baseAddress, int mask, boolean included, boolean isV4) { + IpAddress(BigInteger baseAddress, int mask, boolean included, boolean isV4) { this.netAddress = baseAddress; this.networkMask = mask; this.included = included; @@ -138,26 +143,26 @@ public class NetworkSpace { } - public ipAddress[] split() { - ipAddress firstHalf = new ipAddress(getFirstAddress(), networkMask + 1, included, isV4); - ipAddress secondHalf = new ipAddress(firstHalf.getLastAddress().add(BigInteger.ONE), networkMask + 1, included, isV4); + public IpAddress[] split() { + IpAddress firstHalf = new IpAddress(getFirstAddress(), networkMask + 1, included, isV4); + IpAddress secondHalf = new IpAddress(firstHalf.getLastAddress().add(BigInteger.ONE), networkMask + 1, included, isV4); if (BuildConfig.DEBUG) - Assert.assertTrue(secondHalf.getLastAddress().equals(getLastAddress())); - return new ipAddress[]{firstHalf, secondHalf}; + assertTrue(secondHalf.getLastAddress().equals(getLastAddress())); + return new IpAddress[]{firstHalf, secondHalf}; } String getIPv4Address() { if (BuildConfig.DEBUG) { - Assert.assertTrue(isV4); - Assert.assertTrue(netAddress.longValue() <= 0xffffffffl); - Assert.assertTrue(netAddress.longValue() >= 0); + assertTrue(isV4); + assertTrue(netAddress.longValue() <= 0xffffffffl); + assertTrue(netAddress.longValue() >= 0); } long ip = netAddress.longValue(); return String.format(Locale.US, "%d.%d.%d.%d", (ip >> 24) % 256, (ip >> 16) % 256, (ip >> 8) % 256, ip % 256); } String getIPv6Address() { - if (BuildConfig.DEBUG) Assert.assertTrue(!isV4); + if (BuildConfig.DEBUG) assertTrue(!isV4); BigInteger r = netAddress; String ipv6str = null; @@ -186,7 +191,7 @@ public class NetworkSpace { return ipv6str; } - public boolean containsNet(ipAddress network) { + public boolean containsNet(IpAddress network) { // this.first >= net.first && this.last <= net.last BigInteger ourFirst = getFirstAddress(); BigInteger ourLast = getLastAddress(); @@ -201,12 +206,12 @@ public class NetworkSpace { } - TreeSet mIpAddresses = new TreeSet(); + TreeSet mIpAddresses = new TreeSet(); - public Collection getNetworks(boolean included) { - Vector ips = new Vector(); - for (ipAddress ip : mIpAddresses) { + public Collection getNetworks(boolean included) { + Vector ips = new Vector(); + for (IpAddress ip : mIpAddresses) { if (ip.included == included) ips.add(ip); } @@ -220,35 +225,35 @@ public class NetworkSpace { void addIP(CIDRIP cidrIp, boolean include) { - mIpAddresses.add(new ipAddress(cidrIp, include)); + mIpAddresses.add(new IpAddress(cidrIp, include)); } public void addIPSplit(CIDRIP cidrIp, boolean include) { - ipAddress newIP = new ipAddress(cidrIp, include); - ipAddress[] splitIps = newIP.split(); - for (ipAddress split : splitIps) + IpAddress newIP = new IpAddress(cidrIp, include); + IpAddress[] splitIps = newIP.split(); + for (IpAddress split : splitIps) mIpAddresses.add(split); } void addIPv6(Inet6Address address, int mask, boolean included) { - mIpAddresses.add(new ipAddress(address, mask, included)); + mIpAddresses.add(new IpAddress(address, mask, included)); } - TreeSet generateIPList() { + TreeSet generateIPList() { - PriorityQueue networks = new PriorityQueue(mIpAddresses); + PriorityQueue networks = new PriorityQueue(mIpAddresses); - TreeSet ipsDone = new TreeSet(); + TreeSet ipsDone = new TreeSet(); - ipAddress currentNet = networks.poll(); + IpAddress currentNet = networks.poll(); if (currentNet == null) return ipsDone; while (currentNet != null) { // Check if it and the next of it are compatible - ipAddress nextNet = networks.poll(); + IpAddress nextNet = networks.poll(); - if (BuildConfig.DEBUG) Assert.assertNotNull(currentNet); + if (BuildConfig.DEBUG) assertTrue(currentNet!=null); if (nextNet == null || currentNet.getLastAddress().compareTo(nextNet.getFirstAddress()) == -1) { // Everything good, no overlapping nothing to do ipsDone.add(currentNet); @@ -263,7 +268,7 @@ public class NetworkSpace { currentNet = nextNet; } else { // our currentNet is included in next and types differ. Need to split the next network - ipAddress[] newNets = nextNet.split(); + IpAddress[] newNets = nextNet.split(); // TODO: The contains method of the Priority is stupid linear search @@ -274,7 +279,7 @@ public class NetworkSpace { if (newNets[0].getLastAddress().equals(currentNet.getLastAddress())) { if (BuildConfig.DEBUG) - Assert.assertEquals(newNets[0].networkMask, currentNet.networkMask); + assertTrue(newNets[0].networkMask == currentNet.networkMask); // Don't add the lower half that would conflict with currentNet } else { if (!networks.contains(newNets[0])) @@ -284,9 +289,9 @@ public class NetworkSpace { } } else { if (BuildConfig.DEBUG) { - Assert.assertTrue(currentNet.networkMask < nextNet.networkMask); - Assert.assertTrue(nextNet.getFirstAddress().compareTo(currentNet.getFirstAddress()) == 1); - Assert.assertTrue(currentNet.getLastAddress().compareTo(nextNet.getLastAddress()) != -1); + assertTrue(currentNet.networkMask < nextNet.networkMask); + assertTrue(nextNet.getFirstAddress().compareTo(currentNet.getFirstAddress()) == 1); + assertTrue(currentNet.getLastAddress().compareTo(nextNet.getLastAddress()) != -1); } // This network is bigger than the next and last ip of current >= next @@ -296,13 +301,13 @@ public class NetworkSpace { // simply ignore the next and move on } else { // We need to split our network - ipAddress[] newNets = currentNet.split(); + IpAddress[] newNets = currentNet.split(); if (newNets[1].networkMask == nextNet.networkMask) { if (BuildConfig.DEBUG) { - Assert.assertTrue(newNets[1].getFirstAddress().equals(nextNet.getFirstAddress())); - Assert.assertTrue(newNets[1].getLastAddress().equals(currentNet.getLastAddress())); + assertTrue(newNets[1].getFirstAddress().equals(nextNet.getFirstAddress())); + assertTrue(newNets[1].getLastAddress().equals(currentNet.getLastAddress())); // split second equal the next network, do not add it } networks.add(nextNet); @@ -322,11 +327,11 @@ public class NetworkSpace { return ipsDone; } - Collection getPositiveIPList() { - TreeSet ipsSorted = generateIPList(); + Collection getPositiveIPList() { + TreeSet ipsSorted = generateIPList(); - Vector ips = new Vector(); - for (ipAddress ia : ipsSorted) { + Vector ips = new Vector(); + for (IpAddress ia : ipsSorted) { if (ia.included) ips.add(ia); } @@ -334,7 +339,7 @@ public class NetworkSpace { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { // Include postive routes from the original set under < 4.4 since these might overrule the local // network but only if no smaller negative route exists - for (ipAddress origIp : mIpAddresses) { + for (IpAddress origIp : mIpAddresses) { if (!origIp.included) continue; @@ -345,7 +350,7 @@ public class NetworkSpace { boolean skipIp = false; // If there is any smaller net that is excluded we may not add the positive route back - for (ipAddress calculatedIp : ipsSorted) { + for (IpAddress calculatedIp : ipsSorted) { if (!calculatedIp.included && origIp.containsNet(calculatedIp)) { skipIp = true; break; diff --git a/app/src/main/java/de/blinkt/openvpn/core/NetworkUtils.java b/app/src/main/java/de/blinkt/openvpn/core/NetworkUtils.java new file mode 100644 index 00000000..40449118 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/core/NetworkUtils.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2012-2018 Arne Schwabe + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt + */ + +package de.blinkt.openvpn.core; + +import android.content.Context; +import android.net.*; +import android.os.Build; +import android.text.TextUtils; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.util.Vector; + +public class NetworkUtils { + + public static Vector getLocalNetworks(Context c, boolean ipv6) { + Vector nets = new Vector<>(); + ConnectivityManager conn = (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + Network[] networks = conn.getAllNetworks(); + for (Network network : networks) { + NetworkInfo ni = conn.getNetworkInfo(network); + LinkProperties li = conn.getLinkProperties(network); + + NetworkCapabilities nc = conn.getNetworkCapabilities(network); + + // Skip VPN networks like ourselves + if (nc.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) + continue; + + // Also skip mobile networks + if (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) + continue; + + + for (LinkAddress la : li.getLinkAddresses()) { + if ((la.getAddress() instanceof Inet4Address && !ipv6) || + (la.getAddress() instanceof Inet6Address && ipv6)) + nets.add(la.toString()); + } + } + } else { + // Old Android Version, use native utils via ifconfig instead + // Add local network interfaces + if (ipv6) + return nets; + + String[] localRoutes = NativeUtils.getIfconfig(); + + // The format of mLocalRoutes is kind of broken because I don't really like JNI + for (int i = 0; i < localRoutes.length; i += 3) { + String intf = localRoutes[i]; + String ipAddr = localRoutes[i + 1]; + String netMask = localRoutes[i + 2]; + + if (intf == null || intf.equals("lo") || + intf.startsWith("tun") || intf.startsWith("rmnet")) + continue; + + if (ipAddr == null || netMask == null) { + VpnStatus.logError("Local routes are broken?! (Report to author) " + TextUtils.join("|", localRoutes)); + continue; + } + nets.add(ipAddr + "/" + CIDRIP.calculateLenFromMask(netMask)); + + } + + } + return nets; + } + +} \ No newline at end of file diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java index f701b7aa..11bc4da3 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java @@ -7,8 +7,12 @@ package de.blinkt.openvpn.core; import android.Manifest.permission; import android.annotation.TargetApi; +import android.app.Activity; import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; import android.app.UiModeManager; +import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; @@ -16,14 +20,17 @@ import android.content.pm.ShortcutManager; import android.content.res.Configuration; import android.content.res.Resources; import android.net.ConnectivityManager; +import android.net.Uri; import android.net.VpnService; import android.os.Build; +import android.os.Bundle; import android.os.Handler; import android.os.Handler.Callback; import android.os.IBinder; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.support.annotation.NonNull; import android.support.annotation.RequiresApi; import android.system.OsConstants; import android.text.TextUtils; @@ -32,6 +39,7 @@ import android.widget.Toast; import java.io.IOException; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; @@ -39,6 +47,8 @@ import java.util.Collection; import java.util.Locale; import java.util.Vector; +import de.blinkt.openvpn.LaunchVPN; +import se.leap.bitmaskclient.R; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.VpnStatus.ByteCountListener; import de.blinkt.openvpn.core.VpnStatus.StateListener; @@ -47,7 +57,7 @@ import se.leap.bitmaskclient.VpnNotificationManager; import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_CONNECTED; import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT; -import static de.blinkt.openvpn.core.NetworkSpace.ipAddress; +import static de.blinkt.openvpn.core.NetworkSpace.IpAddress; public class OpenVPNService extends VpnService implements StateListener, Callback, ByteCountListener, IOpenVPNServiceInternal, VpnNotificationManager.VpnServiceCallback { @@ -55,12 +65,12 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac public static final String START_SERVICE_STICKY = "de.blinkt.openvpn.START_SERVICE_STICKY"; public static final String ALWAYS_SHOW_NOTIFICATION = "de.blinkt.openvpn.NOTIFICATION_ALWAYS_VISIBLE"; public static final String DISCONNECT_VPN = "de.blinkt.openvpn.DISCONNECT_VPN"; - private static final String PAUSE_VPN = "de.blinkt.openvpn.PAUSE_VPN"; - private static final String RESUME_VPN = "se.leap.bitmaskclient.RESUME_VPN"; public static final String NOTIFICATION_CHANNEL_BG_ID = "openvpn_bg"; public static final String NOTIFICATION_CHANNEL_NEWSTATUS_ID = "openvpn_newstat"; public static final String VPNSERVICE_TUN = "vpnservice-tun"; - + public final static String ORBOT_PACKAGE_NAME = "org.torproject.android"; + private static final String PAUSE_VPN = "de.blinkt.openvpn.PAUSE_VPN"; + private static final String RESUME_VPN = "se.leap.bitmaskclient.RESUME_VPN"; private static boolean mNotificationAlwaysVisible = false; private final Vector mDnslist = new Vector<>(); private final NetworkSpace mRoutes = new NetworkSpace(); @@ -407,8 +417,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } Runnable processThread; - if (useOpenVPN3) - { + if (useOpenVPN3) { OpenVPNManagement mOpenVPN3 = instantiateOpenVPN3Core(); processThread = (Runnable) mOpenVPN3; mManagement = mOpenVPN3; @@ -545,7 +554,9 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } if (mLocalIP != null) { - addLocalNetworksToRoutes(); + // OpenVPN3 manages excluded local networks by callback + if (!VpnProfile.doUseOpenVPN3(this)) + addLocalNetworksToRoutes(); try { builder.addAddress(mLocalIP.mIp, mLocalIP.len); } catch (IllegalArgumentException iae) { @@ -584,15 +595,15 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac builder.setMtu(mMtu); } - Collection positiveIPv4Routes = mRoutes.getPositiveIPList(); - Collection positiveIPv6Routes = mRoutesv6.getPositiveIPList(); + Collection positiveIPv4Routes = mRoutes.getPositiveIPList(); + Collection positiveIPv6Routes = mRoutesv6.getPositiveIPList(); if ("samsung".equals(Build.BRAND) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && mDnslist.size() >= 1) { // Check if the first DNS Server is in the VPN range try { - ipAddress dnsServer = new ipAddress(new CIDRIP(mDnslist.get(0), 32), true); + IpAddress dnsServer = new IpAddress(new CIDRIP(mDnslist.get(0), 32), true); boolean dnsIncluded = false; - for (ipAddress net : positiveIPv4Routes) { + for (IpAddress net : positiveIPv4Routes) { if (net.containsNet(dnsServer)) { dnsIncluded = true; } @@ -609,9 +620,9 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } } - ipAddress multicastRange = new ipAddress(new CIDRIP("224.0.0.0", 3), true); + IpAddress multicastRange = new IpAddress(new CIDRIP("224.0.0.0", 3), true); - for (NetworkSpace.ipAddress route : positiveIPv4Routes) { + for (IpAddress route : positiveIPv4Routes) { try { if (multicastRange.containsNet(route)) @@ -623,7 +634,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } } - for (NetworkSpace.ipAddress route6 : positiveIPv6Routes) { + for (IpAddress route6 : positiveIPv6Routes) { try { builder.addRoute(route6.getIPv6Address(), route6.networkMask); } catch (IllegalArgumentException ia) { @@ -635,7 +646,16 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac if (mDomain != null) builder.addSearchDomain(mDomain); - VpnStatus.logInfo(R.string.local_ip_info, mLocalIP.mIp, mLocalIP.len, mLocalIPv6, mMtu); + String ipv4info; + int ipv4len; + if (mLocalIP!=null) { + ipv4len=mLocalIP.len; + ipv4info=mLocalIP.mIp; + } else { + ipv4len = -1; + ipv4info="(not set)"; + } + VpnStatus.logInfo(R.string.local_ip_info, ipv4info, ipv4len, mLocalIPv6, mMtu); VpnStatus.logInfo(R.string.dns_server_info, TextUtils.join(", ", mDnslist), mDomain); VpnStatus.logInfo(R.string.routes_info_incl, TextUtils.join(", ", mRoutes.getNetworks(true)), TextUtils.join(", ", mRoutesv6.getNetworks(true))); VpnStatus.logInfo(R.string.routes_info_excl, TextUtils.join(", ", mRoutes.getNetworks(false)), TextUtils.join(", ", mRoutesv6.getNetworks(false))); @@ -643,6 +663,10 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { setAllowedVpnPackages(builder); } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { + // VPN always uses the default network + builder.setUnderlyingNetworks(null); + } String session = mProfile.mName; @@ -650,6 +674,8 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac session = getString(R.string.session_ipv6string, session, mLocalIP, mLocalIPv6); else if (mLocalIP != null) session = getString(R.string.session_ipv4string, session, mLocalIP); + else + session = getString(R.string.session_ipv4string, session, mLocalIPv6); builder.setSession(session); @@ -691,25 +717,11 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } private void addLocalNetworksToRoutes() { - - // Add local network interfaces - String[] localRoutes = NativeUtils.getIfconfig(); - - // The format of mLocalRoutes is kind of broken because I don't really like JNI - for (int i = 0; i < localRoutes.length; i += 3) { - String intf = localRoutes[i]; - String ipAddr = localRoutes[i + 1]; - String netMask = localRoutes[i + 2]; - - if (intf == null || intf.equals("lo") || - intf.startsWith("tun") || intf.startsWith("rmnet")) - continue; - - if (ipAddr == null || netMask == null) { - VpnStatus.logError("Local routes are broken?! (Report to author) " + TextUtils.join("|", localRoutes)); - continue; - } - + for (String net: NetworkUtils.getLocalNetworks(this, false)) + { + String[] netparts = net.split("/"); + String ipAddr = netparts[0]; + int netMask = Integer.parseInt(netparts[1]); if (ipAddr.equals(mLocalIP.mIp)) continue; @@ -719,19 +731,50 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && mProfile.mAllowLocalLAN) mRoutes.addIP(new CIDRIP(ipAddr, netMask), false); } + + // IPv6 is Lollipop+ only so we can skip the lower than KITKAT case + if (mProfile.mAllowLocalLAN) { + for (String net : NetworkUtils.getLocalNetworks(this, true)) { + addRoutev6(net, false);; + } + } + + } @TargetApi(Build.VERSION_CODES.LOLLIPOP) private void setAllowedVpnPackages(Builder builder) { + boolean profileUsesOrBot = false; + + for (Connection c : mProfile.mConnections) { + if (c.mProxyType == Connection.ProxyType.ORBOT) + profileUsesOrBot = true; + } + + if (profileUsesOrBot) + VpnStatus.logDebug("VPN Profile uses at least one server entry with Orbot. Setting up VPN so that OrBot is not redirected over VPN."); + + boolean atLeastOneAllowedApp = false; + + if (mProfile.mAllowedAppsVpnAreDisallowed && profileUsesOrBot) { + try { + builder.addDisallowedApplication(ORBOT_PACKAGE_NAME); + } catch (PackageManager.NameNotFoundException e) { + VpnStatus.logDebug("Orbot not installed?"); + } + } + for (String pkg : mProfile.mAllowedAppsVpn) { try { if (mProfile.mAllowedAppsVpnAreDisallowed) { builder.addDisallowedApplication(pkg); } else { - builder.addAllowedApplication(pkg); - atLeastOneAllowedApp = true; + if (!(profileUsesOrBot && pkg.equals(ORBOT_PACKAGE_NAME))) { + builder.addAllowedApplication(pkg); + atLeastOneAllowedApp = true; + } } } catch (PackageManager.NameNotFoundException e) { mProfile.mAllowedAppsVpn.remove(pkg); @@ -776,13 +819,13 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac CIDRIP route = new CIDRIP(dest, mask); boolean include = isAndroidTunDevice(device); - NetworkSpace.ipAddress gatewayIP = new NetworkSpace.ipAddress(new CIDRIP(gateway, 32), false); + IpAddress gatewayIP = new IpAddress(new CIDRIP(gateway, 32), false); if (mLocalIP == null) { VpnStatus.logError("Local IP address unset and received. Neither pushed server config nor local config specifies an IP addresses. Opening tun device is most likely going to fail."); return; } - NetworkSpace.ipAddress localNet = new NetworkSpace.ipAddress(mLocalIP, true); + IpAddress localNet = new IpAddress(mLocalIP, true); if (localNet.containsNet(gatewayIP)) include = true; @@ -802,10 +845,13 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } public void addRoutev6(String network, String device) { - String[] v6parts = network.split("/"); + // Tun is opened after ROUTE6, no device name may be present boolean included = isAndroidTunDevice(device); + addRoutev6(network, included); + } - // Tun is opened after ROUTE6, no device name may be present + public void addRoutev6(String network, boolean included) { + String[] v6parts = network.split("/"); try { Inet6Address ip = (Inet6Address) InetAddress.getAllByName(v6parts[0])[0]; @@ -870,7 +916,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac if (mLocalIP.len <= 31 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { CIDRIP interfaceRoute = new CIDRIP(mLocalIP.mIp, mLocalIP.len); interfaceRoute.normalise(); - addRoute(interfaceRoute ,true); + addRoute(interfaceRoute, true); } @@ -895,11 +941,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac // Display byte count only after being connected { - if (level == LEVEL_WAITING_FOR_USER_INPUT) { - // The user is presented a dialog of some kind, no need to inform the user - // with a notifcation - return; - } else if (level == LEVEL_CONNECTED) { + if (level == LEVEL_CONNECTED) { mDisplayBytecount = true; mConnecttime = System.currentTimeMillis(); if (!runningOnAndroidTV()) @@ -1005,4 +1047,51 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac public void onNotificationStop() { stopForeground(true); } + + public void trigger_url_open(String info) { + /* + String channel = NOTIFICATION_CHANNEL_USERREQ_ID; + + + String url = info.split(":",2)[1]; + + NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + + Notification.Builder nbuilder = new Notification.Builder(this); + nbuilder.setContentTitle(getString(R.string.openurl_requested)); + + nbuilder.setContentText(url); + nbuilder.setAutoCancel(true); + + int icon = android.R.drawable.ic_dialog_info; + + nbuilder.setSmallIcon(icon); + + Intent openUrlIntent = new Intent(Intent.ACTION_VIEW); + openUrlIntent.setData(Uri.parse(url)); + openUrlIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + nbuilder.setContentIntent(PendingIntent.getActivity(this,0, openUrlIntent, 0)); + + + // Try to set the priority available since API 16 (Jellybean) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) + jbNotificationExtras(PRIORITY_MAX, nbuilder); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + lpNotificationExtras(nbuilder, Notification.CATEGORY_STATUS); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + //noinspection NewApi + nbuilder.setChannelId(channel); + } + + @SuppressWarnings("deprecation") + Notification notification = nbuilder.getNotification(); + + int notificationId = channel.hashCode(); + + mNotificationManager.notify(notificationId, notification); + */ + } } \ No newline at end of file diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java index 2b6df9af..4f7a5bda 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java @@ -6,16 +6,20 @@ package de.blinkt.openvpn.core; import android.content.Context; +import android.content.Intent; import android.net.LocalServerSocket; import android.net.LocalSocket; import android.net.LocalSocketAddress; +import android.os.Build; import android.os.Handler; import android.os.ParcelFileDescriptor; import android.support.annotation.NonNull; -import android.text.TextUtils; +import android.support.annotation.RequiresApi; +import android.system.ErrnoException; +import android.system.Os; import android.util.Log; - -import junit.framework.Assert; +import se.leap.bitmaskclient.R; +import de.blinkt.openvpn.VpnProfile; import java.io.FileDescriptor; import java.io.IOException; @@ -24,19 +28,13 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.InetSocketAddress; import java.net.SocketAddress; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedList; -import java.util.Locale; -import java.util.Vector; - -import se.leap.bitmaskclient.BuildConfig; -import se.leap.bitmaskclient.R; -import de.blinkt.openvpn.VpnProfile; +import java.util.*; public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { + public static final int ORBOT_TIMEOUT_MS = 20 * 1000; private static final String TAG = "openvpn"; + private static final Vector active = new Vector<>(); private final Handler mResumeHandler; private LocalSocket mSocket; private VpnProfile mProfile; @@ -45,13 +43,55 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { private LocalServerSocket mServerSocket; private boolean mWaitingForRelease = false; private long mLastHoldRelease = 0; - - private static final Vector active = new Vector<>(); private LocalSocket mServerSocketLocal; private pauseReason lastPauseReason = pauseReason.noNetwork; private PausedStateCallback mPauseCallback; private boolean mShuttingDown; + private Runnable mResumeHoldRunnable = () -> { + if (shouldBeRunning()) { + releaseHoldCmd(); + } + }; + private Runnable orbotStatusTimeOutRunnable = new Runnable() { + @Override + public void run() { + sendProxyCMD(Connection.ProxyType.SOCKS5, "127.0.0.1", Integer.toString(OrbotHelper.SOCKS_PROXY_PORT_DEFAULT), false); + OrbotHelper.get(mOpenVPNService).removeStatusCallback(statusCallback); + + } + }; + private OrbotHelper.StatusCallback statusCallback = new OrbotHelper.StatusCallback() { + + @Override + public void onStatus(Intent statusIntent) { + StringBuilder extras = new StringBuilder(); + for (String key : statusIntent.getExtras().keySet()) { + Object val = statusIntent.getExtras().get(key); + + extras.append(String.format(Locale.ENGLISH, "%s - '%s'", key, val == null ? "null" : val.toString())); + } + VpnStatus.logDebug("Got Orbot status: " + extras); + } + + @Override + public void onNotYetInstalled() { + VpnStatus.logDebug("Orbot not yet installed"); + } + + @Override + public void onOrbotReady(Intent intent, String socksHost, int socksPort) { + mResumeHandler.removeCallbacks(orbotStatusTimeOutRunnable); + sendProxyCMD(Connection.ProxyType.SOCKS5, socksHost, Integer.toString(socksPort), false); + OrbotHelper.get(mOpenVPNService).removeStatusCallback(this); + } + + @Override + public void onDisabled(Intent intent) { + VpnStatus.logWarning("Orbot integration for external applications is disabled. Waiting %ds before connecting to the default port. Enable external app integration in Orbot or use Socks v5 config instead of Orbot to avoid this delay."); + } + }; + private transient Connection mCurrentProxyConnection; public OpenVpnManagementThread(VpnProfile profile, OpenVPNService openVpnService) { mProfile = profile; @@ -60,14 +100,21 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { } - private Runnable mResumeHoldRunnable = new Runnable() { - @Override - public void run() { - if (shouldBeRunning()) { - releaseHoldCmd(); + private static boolean stopOpenVPN() { + synchronized (active) { + boolean sendCMD = false; + for (OpenVpnManagementThread mt : active) { + sendCMD = mt.managmentCommand("signal SIGINT\n"); + try { + if (mt.mSocket != null) + mt.mSocket.close(); + } catch (IOException e) { + // Ignore close error on already closed socket + } } + return sendCMD; } - }; + } public boolean openManagementInterface(@NonNull Context c) { // Could take a while to open connection @@ -122,7 +169,6 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { return false; } - @Override public void run() { byte[] buffer = new byte[2048]; @@ -198,9 +244,13 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { //ParcelFileDescriptor pfd = ParcelFileDescriptor.fromFd(fdint); //pfd.close(); - NativeUtils.jniclose(fdint); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + fdCloseLollipop(fd); + } else { + NativeUtils.jniclose(fdint); + } return; - } catch (NoSuchMethodException | IllegalArgumentException | InvocationTargetException | IllegalAccessException | NullPointerException e) { + } catch ( NoSuchMethodException | IllegalArgumentException | InvocationTargetException | IllegalAccessException | NullPointerException e) { VpnStatus.logException("Failed to retrieve fd from socket (" + fd + ")", e); } @@ -208,6 +258,15 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { } + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + private void fdCloseLollipop(FileDescriptor fd) { + try { + Os.close(fd); + } catch (Exception e) { + VpnStatus.logException("Failed to close fd (" + fd + ")", e); + } + } + private String processInput(String pendingInput) { @@ -223,11 +282,9 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { return pendingInput; } - private void processCommand(String command) { //Log.i(TAG, "Line from managment" + command); - if (command.startsWith(">") && command.contains(":")) { String[] parts = command.split(":", 2); String cmd = parts[0].substring(1); @@ -263,6 +320,9 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { case "PK_SIGN": processSignCommand(argument); break; + case "INFOMSG": + processInfoMessage(argument); + break; default: VpnStatus.logWarning("MGMT: Got unrecognized command" + command); Log.i(TAG, "Got unrecognized command" + command); @@ -281,6 +341,14 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { } } + private void processInfoMessage(String info) + { + if (info.startsWith("OPEN_URL:")) + { + mOpenVPNService.trigger_url_open(info); + } + } + private void processLogMessage(String argument) { String[] args = argument.split(",", 4); // 0 unix time stamp @@ -367,7 +435,6 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { //managmentCommand("log on all\n"); } - public void releaseHold() { if (mWaitingForRelease) releaseHoldCmd(); @@ -375,27 +442,81 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { private void processProxyCMD(String argument) { String[] args = argument.split(",", 3); - SocketAddress proxyaddr = ProxyDetection.detectProxy(mProfile); + Connection.ProxyType proxyType = Connection.ProxyType.NONE; + + int connectionEntryNumber = Integer.parseInt(args[0]) - 1; + String proxyport = null; + String proxyname = null; + boolean proxyUseAuth = false; + + if (mProfile.mConnections.length > connectionEntryNumber) { + Connection connection = mProfile.mConnections[connectionEntryNumber]; + proxyType = connection.mProxyType; + proxyname = connection.mProxyName; + proxyport = connection.mProxyPort; + proxyUseAuth = connection.mUseProxyAuth; + + // Use transient variable to remember http user/password + mCurrentProxyConnection = connection; + + } else { + VpnStatus.logError(String.format(Locale.ENGLISH, "OpenVPN is asking for a proxy of an unknown connection entry (%d)", connectionEntryNumber)); + } - if (args.length >= 2) { + // atuo detection of proxy + if (proxyType == Connection.ProxyType.NONE) { + SocketAddress proxyaddr = ProxyDetection.detectProxy(mProfile); + if (proxyaddr instanceof InetSocketAddress) { + InetSocketAddress isa = (InetSocketAddress) proxyaddr; + proxyType = Connection.ProxyType.HTTP; + proxyname = isa.getHostName(); + proxyport = String.valueOf(isa.getPort()); + proxyUseAuth = false; + + } + } + + + if (args.length >= 2 && proxyType == Connection.ProxyType.HTTP) { String proto = args[1]; if (proto.equals("UDP")) { - proxyaddr = null; + proxyname = null; + VpnStatus.logInfo("Not using an HTTP proxy since the connection uses UDP"); } } - if (proxyaddr instanceof InetSocketAddress) { - InetSocketAddress isa = (InetSocketAddress) proxyaddr; - VpnStatus.logInfo(R.string.using_proxy, isa.getHostName(), isa.getPort()); + if (proxyType == Connection.ProxyType.ORBOT) { + VpnStatus.updateStateString("WAIT_ORBOT", "Waiting for Orbot to start", R.string.state_waitorbot, ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET); + OrbotHelper orbotHelper = OrbotHelper.get(mOpenVPNService); + if (!orbotHelper.checkTorReceier(mOpenVPNService)) + VpnStatus.logError("Orbot does not seem to be installed!"); + + mResumeHandler.postDelayed(orbotStatusTimeOutRunnable, ORBOT_TIMEOUT_MS); + orbotHelper.addStatusCallback(mOpenVPNService, statusCallback); + + orbotHelper.sendOrbotStartAndStatusBroadcast(); + + } else { + sendProxyCMD(proxyType, proxyname, proxyport, proxyUseAuth); + } + } + + private void sendProxyCMD(Connection.ProxyType proxyType, String proxyname, String proxyport, boolean usePwAuth) { + if (proxyType != Connection.ProxyType.NONE && proxyname != null) { + + VpnStatus.logInfo(R.string.using_proxy, proxyname, proxyname); - String proxycmd = String.format(Locale.ENGLISH, "proxy HTTP %s %d\n", isa.getHostName(), isa.getPort()); + String pwstr = usePwAuth ? " auto" : ""; + + String proxycmd = String.format(Locale.ENGLISH, "proxy %s %s %s%s\n", + proxyType == Connection.ProxyType.HTTP ? "HTTP" : "SOCKS", + proxyname, proxyport, pwstr); managmentCommand(proxycmd); } else { managmentCommand("proxy NONE\n"); } - } private void processState(String argument) { @@ -408,7 +529,6 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { VpnStatus.updateStateString(currentstate, args[2]); } - private void processByteCount(String argument) { // >BYTECOUNT:{BYTES_IN},{BYTES_OUT} int comma = argument.indexOf(','); @@ -419,7 +539,6 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { } - private void processNeedCommand(String argument) { int p1 = argument.indexOf('\''); int p2 = argument.indexOf('\'', p1 + 1); @@ -452,7 +571,8 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { */ if (routeparts.length == 5) { - if (BuildConfig.DEBUG) Assert.assertEquals("dev", routeparts[3]); + //if (BuildConfig.DEBUG) + // assertEquals("dev", routeparts[3]); mOpenVPNService.addRoute(routeparts[0], routeparts[1], routeparts[2], routeparts[4]); } else if (routeparts.length >= 3) { mOpenVPNService.addRoute(routeparts[0], routeparts[1], routeparts[2], null); @@ -473,8 +593,10 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { mOpenVPNService.setLocalIP(ifconfigparts[0], ifconfigparts[1], mtu, ifconfigparts[3]); break; case "IFCONFIG6": - mOpenVPNService.setLocalIPv6(extra); - + String[] ifconfig6parts = extra.split(" "); + mtu = Integer.parseInt(ifconfig6parts[1]); + mOpenVPNService.setMtu(mtu); + mOpenVPNService.setLocalIPv6(ifconfig6parts[0]); break; case "PERSIST_TUN_ACTION": // check if tun cfg stayed the same @@ -546,6 +668,10 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { try { + // Ignore Auth token message, already managed by openvpn itself + if (argument.startsWith("Auth-Token:")) { + return; + } int p1 = argument.indexOf('\''); int p2 = argument.indexOf('\'', p1 + 1); @@ -560,17 +686,26 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { } String pw = null; + String username = null; if (needed.equals("Private Key")) { pw = mProfile.getPasswordPrivateKey(); } else if (needed.equals("Auth")) { pw = mProfile.getPasswordAuth(); + username = mProfile.mUsername; - String usercmd = String.format("username '%s' %s\n", - needed, VpnProfile.openVpnEscape(mProfile.mUsername)); - managmentCommand(usercmd); + } else if (needed.equals("HTTP Proxy")) { + if( mCurrentProxyConnection != null) { + pw = mCurrentProxyConnection.mProxyAuthPassword; + username = mCurrentProxyConnection.mProxyAuthUser; + } } if (pw != null) { + if (username !=null) { + String usercmd = String.format("username '%s' %s\n", + needed, VpnProfile.openVpnEscape(username)); + managmentCommand(usercmd); + } String cmd = String.format("password '%s' %s\n", needed, VpnProfile.openVpnEscape(pw)); managmentCommand(cmd); } else { @@ -580,28 +715,10 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { } - private void proccessPWFailed(String needed, String args) { VpnStatus.updateStateString("AUTH_FAILED", needed + args, R.string.state_auth_failed, ConnectionStatus.LEVEL_AUTH_FAILED); } - - private static boolean stopOpenVPN() { - synchronized (active) { - boolean sendCMD = false; - for (OpenVpnManagementThread mt : active) { - sendCMD = mt.managmentCommand("signal SIGINT\n"); - try { - if (mt.mSocket != null) - mt.mSocket.close(); - } catch (IOException e) { - // Ignore close error on already closed socket - } - } - return sendCMD; - } - } - @Override public void networkChange(boolean samenetwork) { if (mWaitingForRelease) @@ -634,7 +751,7 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { private void processSignCommand(String b64data) { - String signed_string = mProfile.getSignedData(b64data); + String signed_string = mProfile.getSignedData(mOpenVPNService, b64data, false); if (signed_string == null) { managmentCommand("pk-sig\n"); diff --git a/app/src/main/java/de/blinkt/openvpn/core/OrbotHelper.java b/app/src/main/java/de/blinkt/openvpn/core/OrbotHelper.java new file mode 100644 index 00000000..68f5835f --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/core/OrbotHelper.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2012-2018 Arne Schwabe + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt + */ + +/* + * Portions Copyright 2014-2016 Hans-Christoph Steiner + * Portions Copyright 2012-2016 Nathan Freitas + * Portions Copyright (c) 2016 CommonsWare, LLC + * + * 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 de.blinkt.openvpn.core; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.text.TextUtils; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static de.blinkt.openvpn.core.OpenVPNService.ORBOT_PACKAGE_NAME; + +public class OrbotHelper { + //! Based on the class from NetCipher but stripped down and modified for icsopenvpn + + /** + * {@link Intent} send by Orbot with {@code ON/OFF/STARTING/STOPPING} status + * included as an {@link #EXTRA_STATUS} {@code String}. Your app should + * always receive {@code ACTION_STATUS Intent}s since any other app could + * start Orbot. Also, user-triggered starts and stops will also cause + * {@code ACTION_STATUS Intent}s to be broadcast. + */ + public final static String ACTION_STATUS = "org.torproject.android.intent.action.STATUS"; + public final static String STATUS_ON = "ON"; + public final static String STATUS_STARTS_DISABLED = "STARTS_DISABLED"; + + public final static String STATUS_STARTING = "STARTING"; + public final static String STATUS_STOPPING = "STOPPING"; + public final static String EXTRA_STATUS = "org.torproject.android.intent.extra.STATUS"; + /** + * A request to Orbot to transparently start Tor services + */ + public final static String ACTION_START = "org.torproject.android.intent.action.START"; + public final static String EXTRA_PACKAGE_NAME = "org.torproject.android.intent.extra.PACKAGE_NAME"; + public static final int SOCKS_PROXY_PORT_DEFAULT = 9050; + private static OrbotHelper mInstance; + + String EXTRA_SOCKS_PROXY_HOST = "org.torproject.android.intent.extra.SOCKS_PROXY_HOST"; + String EXTRA_SOCKS_PROXY_PORT = "org.torproject.android.intent.extra.SOCKS_PROXY_PORT"; + private Context mContext; + private Set statusCallbacks = new HashSet<>(); + private BroadcastReceiver orbotStatusReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context c, Intent intent) { + if (TextUtils.equals(intent.getAction(), + OrbotHelper.ACTION_STATUS)) { + for (StatusCallback cb : statusCallbacks) { + cb.onStatus(intent); + } + + String status = intent.getStringExtra(EXTRA_STATUS); + if (TextUtils.equals(status, STATUS_ON)) { + int socksPort = intent.getIntExtra(EXTRA_SOCKS_PROXY_PORT, SOCKS_PROXY_PORT_DEFAULT); + String socksHost = intent.getStringExtra(EXTRA_SOCKS_PROXY_HOST); + if (TextUtils.isEmpty(socksHost)) + socksHost = "127.0.0.1"; + for (StatusCallback cb : statusCallbacks) { + cb.onOrbotReady(intent, socksHost, socksPort); + } + } else if (TextUtils.equals(status, STATUS_STARTS_DISABLED)) { + for (StatusCallback cb : statusCallbacks) + cb.onDisabled(intent); + } + + } + } + }; + + private OrbotHelper() { + + } + + public static OrbotHelper get(OpenVPNService mOpenVPNService) { + if (mInstance == null) + mInstance = new OrbotHelper(); + return mInstance; + } + + /** + * Gets an {@link Intent} for starting Orbot. Orbot will reply with the + * current status to the {@code packageName} of the app in the provided + * {@link Context} (i.e. {@link Context#getPackageName()}. + */ + public static Intent getOrbotStartIntent(Context context) { + Intent intent = new Intent(ACTION_START); + intent.setPackage(ORBOT_PACKAGE_NAME); + intent.putExtra(EXTRA_PACKAGE_NAME, context.getPackageName()); + return intent; + } + + public static boolean checkTorReceier(Context c) { + Intent startOrbot = getOrbotStartIntent(c); + PackageManager pm = c.getPackageManager(); + Intent result = null; + List receivers = + pm.queryBroadcastReceivers(startOrbot, 0); + + return receivers != null && receivers.size() > 0; + } + + /** + * Adds a StatusCallback to be called when we find out that + * Orbot is ready. If Orbot is ready for use, your callback + * will be called with onEnabled() immediately, before this + * method returns. + * + * @param cb a callback + * @return the singleton, for chaining + */ + public synchronized OrbotHelper addStatusCallback(Context c, StatusCallback cb) { + if (statusCallbacks.size() == 0) { + c.getApplicationContext().registerReceiver(orbotStatusReceiver, + new IntentFilter(OrbotHelper.ACTION_STATUS)); + mContext = c.getApplicationContext(); + } + if (!checkTorReceier(c)) + cb.onNotYetInstalled(); + statusCallbacks.add(cb); + return (this); + } + + /** + * Removes an existing registered StatusCallback. + * + * @param cb the callback to remove + * @return the singleton, for chaining + */ + public synchronized void removeStatusCallback(StatusCallback cb) { + statusCallbacks.remove(cb); + if (statusCallbacks.size() == 0) + mContext.unregisterReceiver(orbotStatusReceiver); + } + + public void sendOrbotStartAndStatusBroadcast() { + mContext.sendBroadcast(getOrbotStartIntent(mContext)); + } + + private void startOrbotService(String action) { + Intent clearVPNMode = new Intent(); + clearVPNMode.setComponent(new ComponentName(ORBOT_PACKAGE_NAME, ".service.TorService")); + clearVPNMode.setAction(action); + mContext.startService(clearVPNMode); + } + + public interface StatusCallback { + /** + * Called when Orbot is operational + * + * @param statusIntent an Intent containing information about + * Orbot, including proxy ports + */ + void onStatus(Intent statusIntent); + + + /** + * Called if Orbot is not yet installed. Usually, you handle + * this by checking the return value from init() on OrbotInitializer + * or calling isInstalled() on OrbotInitializer. However, if + * you have need for it, if a callback is registered before + * an init() call determines that Orbot is not installed, your + * callback will be called with onNotYetInstalled(). + */ + void onNotYetInstalled(); + + void onOrbotReady(Intent intent, String socksHost, int socksPort); + + /** + * Called if Orbot background control is disabled. + * @param intent the intent delivered + */ + void onDisabled(Intent intent); + } +} diff --git a/app/src/main/java/de/blinkt/openvpn/core/ProfileManager.java b/app/src/main/java/de/blinkt/openvpn/core/ProfileManager.java index f776fc2e..b9edc4b2 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/ProfileManager.java +++ b/app/src/main/java/de/blinkt/openvpn/core/ProfileManager.java @@ -128,6 +128,7 @@ public class ProfileManager { } public static void setTemporaryProfile(Context c, VpnProfile tmp) { + tmp.mTemporaryProfile = true; ProfileManager.tmpprofile = tmp; saveProfile(c, tmp, true, true); } @@ -174,8 +175,9 @@ public class ProfileManager { vlist.add(TEMPORARY_PROFILE_FILENAME); for (String vpnentry : vlist) { + ObjectInputStream vpnfile=null; try { - ObjectInputStream vpnfile = new ObjectInputStream(context.openFileInput(vpnentry + ".vp")); + vpnfile = new ObjectInputStream(context.openFileInput(vpnentry + ".vp")); VpnProfile vp = ((VpnProfile) vpnfile.readObject()); // Sanity check @@ -189,9 +191,18 @@ public class ProfileManager { profiles.put(vp.getUUID().toString(), vp); } + } catch (IOException | ClassNotFoundException e) { if (!vpnentry.equals(TEMPORARY_PROFILE_FILENAME)) VpnStatus.logException("Loading VPN List", e); + } finally { + if (vpnfile!=null) { + try { + vpnfile.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } } } } diff --git a/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java b/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java index 97a73964..810974df 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java +++ b/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java @@ -48,7 +48,7 @@ public class VPNLaunchHelper { } } - return null; + throw new RuntimeException("Cannot find any execulte for this device's ABIs " + abis.toString()); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) diff --git a/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java b/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java index a9cc4f18..5fbb440b 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java +++ b/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java @@ -300,6 +300,8 @@ public class VpnStatus { return R.string.state_resolve; case "TCP_CONNECT": return R.string.state_tcp_connect; + case "AUTH_PENDING": + return R.string.state_auth_pending; default: return R.string.unknown_state; } @@ -323,7 +325,7 @@ public class VpnStatus { private static ConnectionStatus getLevel(String state) { String[] noreplyet = {"CONNECTING", "WAIT", "RECONNECTING", "RESOLVE", "TCP_CONNECT"}; - String[] reply = {"AUTH", "GET_CONFIG", "ASSIGN_IP", "ADD_ROUTES"}; + String[] reply = {"AUTH", "GET_CONFIG", "ASSIGN_IP", "ADD_ROUTES", "AUTH_PENDING"}; String[] connected = {"CONNECTED"}; String[] notconnected = {"DISCONNECTED", "EXITING"}; @@ -385,7 +387,7 @@ public class VpnStatus { for (StateListener sl : stateListener) { sl.updateState(state, msg, resid, level); } - newLogItem(new LogItem((LogLevel.DEBUG), String.format("New OpenVPN Status (%s->%s): %s",state,level.toString(),msg))); + //newLogItem(new LogItem((LogLevel.DEBUG), String.format("New OpenVPN Status (%s->%s): %s",state,level.toString(),msg))); } public static void logInfo(String message) { -- cgit v1.2.3