summaryrefslogtreecommitdiff
path: root/app/src/main/java/de
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/de')
-rw-r--r--app/src/main/java/de/blinkt/openvpn/LaunchVPN.java4
-rw-r--r--app/src/main/java/de/blinkt/openvpn/VpnProfile.java482
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/CIDRIP.java8
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java379
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/Connection.java33
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/ExtAuthHelper.java259
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java2
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/LogFileHandler.java4
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java22
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java103
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/NetworkUtils.java75
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java181
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java239
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/OrbotHelper.java202
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/ProfileManager.java13
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java2
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java6
17 files changed, 1479 insertions, 535 deletions
diff --git a/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java b/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java
index e83cffa0..5bf178ae 100644
--- a/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java
+++ b/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java
@@ -7,6 +7,8 @@ package de.blinkt.openvpn;
import se.leap.bitmaskclient.R;
+import se.leap.bitmaskclient.R;
+
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
@@ -231,4 +233,4 @@ public class LaunchVPN extends Activity {
VpnStatus.logException("SU command", e);
}
}
-}
+} \ No newline at end of file
diff --git a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java
index 4becdc32..bd28ae16 100644
--- a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java
+++ b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java
@@ -6,7 +6,6 @@
package de.blinkt.openvpn;
import se.leap.bitmaskclient.R;
-
import se.leap.bitmaskclient.BuildConfig;
import android.annotation.SuppressLint;
@@ -16,10 +15,12 @@ import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
+import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.security.KeyChain;
import android.security.KeyChainException;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Base64;
@@ -62,14 +63,9 @@ public class VpnProfile implements Serializable, Cloneable {
public static final String EXTRA_PROFILEUUID = "de.blinkt.openvpn.profileUUID";
public static final String INLINE_TAG = "[[INLINE]]";
public static final String DISPLAYNAME_TAG = "[[NAME]]";
-
- private static final long serialVersionUID = 7085688938959334563L;
public static final int MAXLOGLEVEL = 4;
- public static final int CURRENT_PROFILE_VERSION = 6;
+ public static final int CURRENT_PROFILE_VERSION = 7;
public static final int DEFAULT_MSSFIX_SIZE = 1280;
- public static String DEFAULT_DNS1 = "8.8.8.8";
- public static String DEFAULT_DNS2 = "8.8.4.4";
-
public static final int TYPE_CERTIFICATES = 0;
public static final int TYPE_PKCS12 = 1;
public static final int TYPE_KEYSTORE = 2;
@@ -78,17 +74,20 @@ public class VpnProfile implements Serializable, Cloneable {
public static final int TYPE_USERPASS_CERTIFICATES = 5;
public static final int TYPE_USERPASS_PKCS12 = 6;
public static final int TYPE_USERPASS_KEYSTORE = 7;
+ public static final int TYPE_EXTERNAL_APP = 8;
public static final int X509_VERIFY_TLSREMOTE = 0;
public static final int X509_VERIFY_TLSREMOTE_COMPAT_NOREMAPPING = 1;
public static final int X509_VERIFY_TLSREMOTE_DN = 2;
public static final int X509_VERIFY_TLSREMOTE_RDN = 3;
public static final int X509_VERIFY_TLSREMOTE_RDN_PREFIX = 4;
-
-
public static final int AUTH_RETRY_NONE_FORGET = 0;
- private static final int AUTH_RETRY_NONE_KEEP = 1;
public static final int AUTH_RETRY_NOINTERACT = 2;
+ public static final boolean mIsOpenVPN22 = false;
+ private static final long serialVersionUID = 7085688938959334563L;
+ private static final int AUTH_RETRY_NONE_KEEP = 1;
private static final int AUTH_RETRY_INTERACT = 3;
+ public static String DEFAULT_DNS1 = "8.8.8.8";
+ public static String DEFAULT_DNS2 = "8.8.4.4";
// variable named wrong and should haven beeen transient
// but needs to keep wrong name to guarante loading of old
// profiles
@@ -105,7 +104,6 @@ public class VpnProfile implements Serializable, Cloneable {
public String mPKCS12Filename;
public String mPKCS12Password;
public boolean mUseTLSAuth = false;
-
public String mDNS1 = DEFAULT_DNS1;
public String mDNS2 = DEFAULT_DNS2;
public String mIPv4Address;
@@ -127,7 +125,7 @@ public class VpnProfile implements Serializable, Cloneable {
public String mCustomConfigOptions = "";
public String mVerb = "1"; //ignored
public String mCipher = "";
- public boolean mNobind = false;
+ public boolean mNobind = true;
public boolean mUseDefaultRoutev6 = true;
public String mCustomRoutesv6 = "";
public String mKeyPassword = "";
@@ -139,13 +137,7 @@ public class VpnProfile implements Serializable, Cloneable {
public String mAuth = "";
public int mX509AuthType = X509_VERIFY_TLSREMOTE_RDN;
public String mx509UsernameField = null;
-
- private transient PrivateKey mPrivateKey;
- // Public attributes, since I got mad with getter/setter
- // set members to default values
- private UUID mUuid;
public boolean mAllowLocalLAN;
- private int mProfileVersion;
public String mExcludedRoutes;
public String mExcludedRoutesv6;
public int mMssFix = 0; // -1 is default,
@@ -153,26 +145,26 @@ public class VpnProfile implements Serializable, Cloneable {
public boolean mRemoteRandom = false;
public HashSet<String> mAllowedAppsVpn = new HashSet<>();
public boolean mAllowedAppsVpnAreDisallowed = true;
-
public String mCrlFilename;
public String mProfileCreator;
-
+ public String mExternalAuthenticator;
public int mAuthRetry = AUTH_RETRY_NONE_FORGET;
public int mTunMtu;
-
-
public boolean mPushPeerInfo = false;
- public static final boolean mIsOpenVPN22 = false;
-
public int mVersion = 0;
-
// timestamp when the profile was last used
public long mLastUsed;
-
+ public String importedProfileHash;
/* Options no longer used in new profiles */
public String mServerName = "openvpn.example.com";
public String mServerPort = "1194";
public boolean mUseUdp = true;
+ public boolean mTemporaryProfile = false;
+ private transient PrivateKey mPrivateKey;
+ // Public attributes, since I got mad with getter/setter
+ // set members to default values
+ private UUID mUuid;
+ private int mProfileVersion;
public VpnProfile(String name) {
@@ -200,6 +192,48 @@ public class VpnProfile implements Serializable, Cloneable {
return '"' + escapedString + '"';
}
+ public static boolean doUseOpenVPN3(Context c) {
+ SharedPreferences prefs = Preferences.getDefaultSharedPreferences(c);
+ boolean useOpenVPN3 = prefs.getBoolean("ovpn3", false);
+ if (!BuildConfig.openvpn3)
+ useOpenVPN3 = false;
+ return useOpenVPN3;
+ }
+
+ //! Put inline data inline and other data as normal escaped filename
+ public static String insertFileData(String cfgentry, String filedata) {
+ if (filedata == null) {
+ return String.format("%s %s\n", cfgentry, "file missing in config profile");
+ } else if (isEmbedded(filedata)) {
+ String dataWithOutHeader = getEmbeddedContent(filedata);
+ return String.format(Locale.ENGLISH, "<%s>\n%s\n</%s>\n", cfgentry, dataWithOutHeader, cfgentry);
+ } else {
+ return String.format(Locale.ENGLISH, "%s %s\n", cfgentry, openVpnEscape(filedata));
+ }
+ }
+
+ public static String getDisplayName(String embeddedFile) {
+ int start = DISPLAYNAME_TAG.length();
+ int end = embeddedFile.indexOf(INLINE_TAG);
+ return embeddedFile.substring(start, end);
+ }
+
+ public static String getEmbeddedContent(String data) {
+ if (!data.contains(INLINE_TAG))
+ return data;
+
+ int start = data.indexOf(INLINE_TAG) + INLINE_TAG.length();
+ return data.substring(start);
+ }
+
+ public static boolean isEmbedded(String data) {
+ if (data == null)
+ return false;
+ if (data.startsWith(INLINE_TAG) || data.startsWith(DISPLAYNAME_TAG))
+ return true;
+ else
+ return false;
+ }
@Override
public boolean equals(Object obj) {
@@ -223,6 +257,7 @@ public class VpnProfile implements Serializable, Cloneable {
mAllowLocalLAN = true;
mPushPeerInfo = false;
mMssFix = 0;
+ mNobind = false;
}
public UUID getUUID() {
@@ -230,6 +265,11 @@ public class VpnProfile implements Serializable, Cloneable {
}
+ // Only used for the special case of managed profiles
+ public void setUUID(UUID uuid) {
+ mUuid = uuid;
+ }
+
public String getName() {
if (TextUtils.isEmpty(mName))
return "No profile name";
@@ -248,6 +288,7 @@ public class VpnProfile implements Serializable, Cloneable {
}
if (mAllowedAppsVpn == null)
mAllowedAppsVpn = new HashSet<>();
+
if (mConnections == null)
mConnections = new Connection[0];
@@ -255,7 +296,11 @@ public class VpnProfile implements Serializable, Cloneable {
if (TextUtils.isEmpty(mProfileCreator))
mUserEditable = true;
}
-
+ if (mProfileVersion < 7) {
+ for (Connection c : mConnections)
+ if (c.mProxyType == null)
+ c.mProxyType = Connection.ProxyType.NONE;
+ }
mProfileVersion = CURRENT_PROFILE_VERSION;
@@ -274,68 +319,61 @@ public class VpnProfile implements Serializable, Cloneable {
}
-
- public static boolean doUseOpenVPN3(Context c) {
- SharedPreferences prefs = Preferences.getDefaultSharedPreferences(c);
- boolean useOpenVPN3 = prefs.getBoolean("ovpn3", false);
- if (!BuildConfig.openvpn3)
- useOpenVPN3 = false;
- return useOpenVPN3;
- }
-
public String getConfigFile(Context context, boolean configForOvpn3) {
File cacheDir = context.getCacheDir();
- String cfg = "";
+ StringBuilder cfg = new StringBuilder();
if (!configForOvpn3) {
// Enable management interface
- cfg += "# Config for OpenVPN 2.x\n";
- cfg += "# Enables connection to GUI\n";
- cfg += "management ";
+ cfg.append("# Config for OpenVPN 2.x\n");
+ cfg.append("# Enables connection to GUI\n");
+ cfg.append("management ");
- cfg += cacheDir.getAbsolutePath() + "/" + "mgmtsocket";
- cfg += " unix\n";
- cfg += "management-client\n";
+ cfg.append(cacheDir.getAbsolutePath()).append("/").append("mgmtsocket");
+ cfg.append(" unix\n");
+ cfg.append("management-client\n");
// Not needed, see updated man page in 2.3
//cfg += "management-signal\n";
- cfg += "management-query-passwords\n";
- cfg += "management-hold\n\n";
+ cfg.append("management-query-passwords\n");
+ cfg.append("management-hold\n\n");
- cfg += String.format("setenv IV_GUI_VER %s \n", openVpnEscape(getVersionEnvString(context)));
+ cfg.append(String.format("setenv IV_GUI_VER %s \n", openVpnEscape(getVersionEnvString(context))));
String versionString = getPlatformVersionEnvString();
- cfg += String.format("setenv IV_PLAT_VER %s\n", openVpnEscape(versionString));
+ cfg.append(String.format("setenv IV_PLAT_VER %s\n", openVpnEscape(versionString)));
} else {
- cfg += "# Config for OpeNVPN 3 C++\n";
+ cfg.append("# Config for OpenVPN 3 C++\n");
}
- cfg += "machine-readable-output\n";
- cfg += "allow-recursive-routing\n";
-
- // Users are confused by warnings that are misleading...
- cfg += "ifconfig-nowarn\n";
+ if (!configForOvpn3) {
+ cfg.append("machine-readable-output\n");
+ if (!mIsOpenVPN22)
+ cfg.append("allow-recursive-routing\n");
+ // Users are confused by warnings that are misleading...
+ cfg.append("ifconfig-nowarn\n");
+ }
boolean useTLSClient = (mAuthenticationType != TYPE_STATICKEYS);
if (useTLSClient && mUsePull)
- cfg += "client\n";
+ cfg.append("client\n");
else if (mUsePull)
- cfg += "pull\n";
+ cfg.append("pull\n");
else if (useTLSClient)
- cfg += "tls-client\n";
+ cfg.append("tls-client\n");
//cfg += "verb " + mVerb + "\n";
- cfg += "verb " + MAXLOGLEVEL + "\n";
+ cfg.append("verb " + MAXLOGLEVEL + "\n");
if (mConnectRetryMax == null) {
mConnectRetryMax = "-1";
}
if (!mConnectRetryMax.equals("-1"))
- cfg += "connect-retry-max " + mConnectRetryMax + "\n";
+ cfg.append("connect-retry-max ").append(mConnectRetryMax).append("\n");
if (TextUtils.isEmpty(mConnectRetry))
mConnectRetry = "2";
@@ -345,34 +383,34 @@ public class VpnProfile implements Serializable, Cloneable {
if (!mIsOpenVPN22)
- cfg += "connect-retry " + mConnectRetry + " " + mConnectRetryMaxTime + "\n";
- else if (mIsOpenVPN22 && mUseUdp)
- cfg += "connect-retry " + mConnectRetry + "\n";
+ cfg.append("connect-retry ").append(mConnectRetry).append(" ").append(mConnectRetryMaxTime).append("\n");
+ else if (mIsOpenVPN22 && !mUseUdp)
+ cfg.append("connect-retry ").append(mConnectRetry).append("\n");
- cfg += "resolv-retry 60\n";
+ cfg.append("resolv-retry 60\n");
// We cannot use anything else than tun
- cfg += "dev tun\n";
+ cfg.append("dev tun\n");
boolean canUsePlainRemotes = true;
if (mConnections.length == 1) {
- cfg += mConnections[0].getConnectionBlock();
+ cfg.append(mConnections[0].getConnectionBlock(configForOvpn3));
} else {
for (Connection conn : mConnections) {
canUsePlainRemotes = canUsePlainRemotes && conn.isOnlyRemote();
}
if (mRemoteRandom)
- cfg += "remote-random\n";
+ cfg.append("remote-random\n");
if (canUsePlainRemotes) {
for (Connection conn : mConnections) {
if (conn.mEnabled) {
- cfg += conn.getConnectionBlock();
+ cfg.append(conn.getConnectionBlock(configForOvpn3));
}
}
}
@@ -381,91 +419,101 @@ public class VpnProfile implements Serializable, Cloneable {
switch (mAuthenticationType) {
case VpnProfile.TYPE_USERPASS_CERTIFICATES:
- cfg += "auth-user-pass\n";
+ cfg.append("auth-user-pass\n");
case VpnProfile.TYPE_CERTIFICATES:
// Ca
- cfg += insertFileData("ca", mCaFilename);
+ cfg.append(insertFileData("ca", mCaFilename));
// Client Cert + Key
- cfg += insertFileData("key", mClientKeyFilename);
- cfg += insertFileData("cert", mClientCertFilename);
+ cfg.append(insertFileData("key", mClientKeyFilename));
+ cfg.append(insertFileData("cert", mClientCertFilename));
break;
case VpnProfile.TYPE_USERPASS_PKCS12:
- cfg += "auth-user-pass\n";
+ cfg.append("auth-user-pass\n");
case VpnProfile.TYPE_PKCS12:
- cfg += insertFileData("pkcs12", mPKCS12Filename);
+ cfg.append(insertFileData("pkcs12", mPKCS12Filename));
+
+ if (!TextUtils.isEmpty(mCaFilename))
+ {
+ cfg.append(insertFileData("ca", mCaFilename));
+ }
break;
case VpnProfile.TYPE_USERPASS_KEYSTORE:
- cfg += "auth-user-pass\n";
+ cfg.append("auth-user-pass\n");
case VpnProfile.TYPE_KEYSTORE:
+ case VpnProfile.TYPE_EXTERNAL_APP:
if (!configForOvpn3) {
- String[] ks = getKeyStoreCertificates(context);
- cfg += "### From Keystore ####\n";
+ String[] ks = getExternalCertificates(context);
+ cfg.append("### From Keystore/ext auth app ####\n");
if (ks != null) {
- cfg += "<ca>\n" + ks[0] + "\n</ca>\n";
- if (ks[1] != null)
- cfg += "<extra-certs>\n" + ks[1] + "\n</extra-certs>\n";
- cfg += "<cert>\n" + ks[2] + "\n</cert>\n";
- cfg += "management-external-key\n";
+ cfg.append("<ca>\n").append(ks[0]).append("\n</ca>\n");
+ if (!TextUtils.isEmpty(ks[1]))
+ cfg.append("<extra-certs>\n").append(ks[1]).append("\n</extra-certs>\n");
+ cfg.append("<cert>\n").append(ks[2]).append("\n</cert>\n");
+ cfg.append("management-external-key nopadding\n");
} else {
- cfg += context.getString(R.string.keychain_access) + "\n";
+ cfg.append(context.getString(R.string.keychain_access)).append("\n");
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN)
if (!mAlias.matches("^[a-zA-Z0-9]$"))
- cfg += context.getString(R.string.jelly_keystore_alphanumeric_bug) + "\n";
+ cfg.append(context.getString(R.string.jelly_keystore_alphanumeric_bug)).append("\n");
}
}
break;
case VpnProfile.TYPE_USERPASS:
- cfg += "auth-user-pass\n";
- cfg += insertFileData("ca", mCaFilename);
+ cfg.append("auth-user-pass\n");
+ cfg.append(insertFileData("ca", mCaFilename));
+ if (configForOvpn3) {
+ // OpenVPN 3 needs to be told that a client certificate is not required
+ cfg.append("client-cert-not-required\n");
+ }
}
if (isUserPWAuth()) {
- if (mAuthenticationType == AUTH_RETRY_NOINTERACT)
- cfg += "auth-retry nointeract";
+ if (mAuthRetry == AUTH_RETRY_NOINTERACT)
+ cfg.append("auth-retry nointeract\n");
}
if (!TextUtils.isEmpty(mCrlFilename))
- cfg += insertFileData("crl-verify", mCrlFilename);
+ cfg.append(insertFileData("crl-verify", mCrlFilename));
if (mUseLzo) {
- cfg += "comp-lzo\n";
+ cfg.append("comp-lzo\n");
}
if (mUseTLSAuth) {
boolean useTlsCrypt = mTLSAuthDirection.equals("tls-crypt");
if (mAuthenticationType == TYPE_STATICKEYS)
- cfg += insertFileData("secret", mTLSAuthFilename);
+ cfg.append(insertFileData("secret", mTLSAuthFilename));
else if (useTlsCrypt)
- cfg += insertFileData("tls-crypt", mTLSAuthFilename);
+ cfg.append(insertFileData("tls-crypt", mTLSAuthFilename));
else
- cfg += insertFileData("tls-auth", mTLSAuthFilename);
+ cfg.append(insertFileData("tls-auth", mTLSAuthFilename));
if (!TextUtils.isEmpty(mTLSAuthDirection) && !useTlsCrypt) {
- cfg += "key-direction ";
- cfg += mTLSAuthDirection;
- cfg += "\n";
+ cfg.append("key-direction ");
+ cfg.append(mTLSAuthDirection);
+ cfg.append("\n");
}
}
if (!mUsePull) {
if (!TextUtils.isEmpty(mIPv4Address))
- cfg += "ifconfig " + cidrToIPAndNetmask(mIPv4Address) + "\n";
+ cfg.append("ifconfig ").append(cidrToIPAndNetmask(mIPv4Address)).append("\n");
if (!TextUtils.isEmpty(mIPv6Address)) {
// Use our own ip as gateway since we ignore it anyway
String fakegw = mIPv6Address.split("/", 2)[0];
- cfg += "ifconfig-ipv6 " + mIPv6Address + " " + fakegw + "\n";
+ cfg.append("ifconfig-ipv6 ").append(mIPv6Address).append(" ").append(fakegw).append("\n");
}
}
if (mUsePull && mRoutenopull)
- cfg += "route-nopull\n";
+ cfg.append("route-nopull\n");
String routes = "";
@@ -483,128 +531,129 @@ public class VpnProfile implements Serializable, Cloneable {
if (mUseDefaultRoutev6)
- cfg += "route-ipv6 ::/0\n";
+ cfg.append("route-ipv6 ::/0\n");
else
for (String route : getCustomRoutesv6(mCustomRoutesv6)) {
routes += "route-ipv6 " + route + "\n";
}
- cfg += routes;
+ cfg.append(routes);
if (mOverrideDNS || !mUsePull) {
if (!TextUtils.isEmpty(mDNS1)) {
- cfg += "dhcp-option DNS " + mDNS1 + "\n";
+ cfg.append("dhcp-option DNS ").append(mDNS1).append("\n");
}
if (!TextUtils.isEmpty(mDNS2)) {
- cfg += "dhcp-option DNS " + mDNS2 + "\n";
+ cfg.append("dhcp-option DNS ").append(mDNS2).append("\n");
}
if (!TextUtils.isEmpty(mSearchDomain))
- cfg += "dhcp-option DOMAIN " + mSearchDomain + "\n";
+ cfg.append("dhcp-option DOMAIN ").append(mSearchDomain).append("\n");
}
if (mMssFix != 0) {
if (mMssFix != 1450) {
- cfg += String.format(Locale.US, "mssfix %d\n", mMssFix);
+ cfg.append(String.format(Locale.US, "mssfix %d\n", mMssFix));
} else
- cfg += "mssfix\n";
+ cfg.append("mssfix\n");
}
if (mTunMtu >= 48 && mTunMtu != 1500) {
- cfg += String.format(Locale.US, "tun-mtu %d\n", mTunMtu);
+ cfg.append(String.format(Locale.US, "tun-mtu %d\n", mTunMtu));
}
if (mNobind)
- cfg += "nobind\n";
+ cfg.append("nobind\n");
// Authentication
if (mAuthenticationType != TYPE_STATICKEYS) {
if (mCheckRemoteCN) {
if (mRemoteCN == null || mRemoteCN.equals(""))
- cfg += "verify-x509-name " + openVpnEscape(mConnections[0].mServerName) + " name\n";
+ cfg.append("verify-x509-name ").append(openVpnEscape(mConnections[0].mServerName)).append(" name\n");
else
switch (mX509AuthType) {
// 2.2 style x509 checks
case X509_VERIFY_TLSREMOTE_COMPAT_NOREMAPPING:
- cfg += "compat-names no-remapping\n";
+ cfg.append("compat-names no-remapping\n");
case X509_VERIFY_TLSREMOTE:
- cfg += "tls-remote " + openVpnEscape(mRemoteCN) + "\n";
+ cfg.append("tls-remote ").append(openVpnEscape(mRemoteCN)).append("\n");
break;
case X509_VERIFY_TLSREMOTE_RDN:
- cfg += "verify-x509-name " + openVpnEscape(mRemoteCN) + " name\n";
+ cfg.append("verify-x509-name ").append(openVpnEscape(mRemoteCN)).append(" name\n");
break;
case X509_VERIFY_TLSREMOTE_RDN_PREFIX:
- cfg += "verify-x509-name " + openVpnEscape(mRemoteCN) + " name-prefix\n";
+ cfg.append("verify-x509-name ").append(openVpnEscape(mRemoteCN)).append(" name-prefix\n");
break;
case X509_VERIFY_TLSREMOTE_DN:
- cfg += "verify-x509-name " + openVpnEscape(mRemoteCN) + "\n";
+ cfg.append("verify-x509-name ").append(openVpnEscape(mRemoteCN)).append("\n");
break;
}
if (!TextUtils.isEmpty(mx509UsernameField))
- cfg += "x509-username-field " + openVpnEscape(mx509UsernameField) + "\n";
+ cfg.append("x509-username-field ").append(openVpnEscape(mx509UsernameField)).append("\n");
}
if (mExpectTLSCert)
- cfg += "remote-cert-tls server\n";
+ cfg.append("remote-cert-tls server\n");
}
if (!TextUtils.isEmpty(mCipher)) {
- cfg += "cipher " + mCipher + "\n";
+ cfg.append("cipher ").append(mCipher).append("\n");
}
if (!TextUtils.isEmpty(mAuth)) {
- cfg += "auth " + mAuth + "\n";
+ cfg.append("auth ").append(mAuth).append("\n");
}
// Obscure Settings dialog
if (mUseRandomHostname)
- cfg += "#my favorite options :)\nremote-random-hostname\n";
+ cfg.append("#my favorite options :)\nremote-random-hostname\n");
if (mUseFloat)
- cfg += "float\n";
+ cfg.append("float\n");
if (mPersistTun) {
- cfg += "persist-tun\n";
- cfg += "# persist-tun also enables pre resolving to avoid DNS resolve problem\n";
- cfg += "preresolve\n";
+ cfg.append("persist-tun\n");
+ cfg.append("# persist-tun also enables pre resolving to avoid DNS resolve problem\n");
+ if (!mIsOpenVPN22)
+ cfg.append("preresolve\n");
}
if (mPushPeerInfo)
- cfg += "push-peer-info\n";
+ cfg.append("push-peer-info\n");
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean usesystemproxy = prefs.getBoolean("usesystemproxy", true);
- if (usesystemproxy && !mIsOpenVPN22 && !configForOvpn3) {
- cfg += "# Use system proxy setting\n";
- cfg += "management-query-proxy\n";
+ if (usesystemproxy && !mIsOpenVPN22 && !configForOvpn3 && !usesExtraProxyOptions()) {
+ cfg.append("# Use system proxy setting\n");
+ cfg.append("management-query-proxy\n");
}
if (mUseCustomConfig) {
- cfg += "# Custom configuration options\n";
- cfg += "# You are on your on own here :)\n";
- cfg += mCustomConfigOptions;
- cfg += "\n";
+ cfg.append("# Custom configuration options\n");
+ cfg.append("# You are on your on own here :)\n");
+ cfg.append(mCustomConfigOptions);
+ cfg.append("\n");
}
if (!canUsePlainRemotes) {
- cfg += "# Connection Options are at the end to allow global options (and global custom options) to influence connection blocks\n";
+ cfg.append("# Connection Options are at the end to allow global options (and global custom options) to influence connection blocks\n");
for (Connection conn : mConnections) {
if (conn.mEnabled) {
- cfg += "<connection>\n";
- cfg += conn.getConnectionBlock();
- cfg += "</connection>\n";
+ cfg.append("<connection>\n");
+ cfg.append(conn.getConnectionBlock(configForOvpn3));
+ cfg.append("</connection>\n");
}
}
}
- return cfg;
+ return cfg.toString();
}
public String getPlatformVersionEnvString() {
@@ -624,18 +673,6 @@ public class VpnProfile implements Serializable, Cloneable {
}
- //! Put inline data inline and other data as normal escaped filename
- public static String insertFileData(String cfgentry, String filedata) {
- if (filedata == null) {
- return String.format("%s %s\n", cfgentry, "file missing in config profile");
- } else if (isEmbedded(filedata)) {
- String dataWithOutHeader = getEmbeddedContent(filedata);
- return String.format(Locale.ENGLISH, "<%s>\n%s\n</%s>\n", cfgentry, dataWithOutHeader, cfgentry);
- } else {
- return String.format(Locale.ENGLISH, "%s %s\n", cfgentry, openVpnEscape(filedata));
- }
- }
-
@NonNull
private Collection<String> getCustomRoutes(String routes) {
Vector<String> cidrRoutes = new Vector<>();
@@ -697,7 +734,6 @@ public class VpnProfile implements Serializable, Cloneable {
return parts[0] + " " + netmask;
}
-
public Intent prepareStartService(Context context) {
Intent intent = getStartServiceIntent(context);
@@ -727,33 +763,6 @@ public class VpnProfile implements Serializable, Cloneable {
return intent;
}
- public String[] getKeyStoreCertificates(Context context) {
- return getKeyStoreCertificates(context, 5);
- }
-
- public static String getDisplayName(String embeddedFile) {
- int start = DISPLAYNAME_TAG.length();
- int end = embeddedFile.indexOf(INLINE_TAG);
- return embeddedFile.substring(start, end);
- }
-
- public static String getEmbeddedContent(String data) {
- if (!data.contains(INLINE_TAG))
- return data;
-
- int start = data.indexOf(INLINE_TAG) + INLINE_TAG.length();
- return data.substring(start);
- }
-
- public static boolean isEmbedded(String data) {
- if (data == null)
- return false;
- if (data.startsWith(INLINE_TAG) || data.startsWith(DISPLAYNAME_TAG))
- return true;
- else
- return false;
- }
-
public void checkForRestart(final Context context) {
/* This method is called when OpenVPNService is restarted */
@@ -762,7 +771,7 @@ public class VpnProfile implements Serializable, Cloneable {
new Thread(new Runnable() {
@Override
public void run() {
- getKeyStoreCertificates(context);
+ getExternalCertificates(context);
}
}).start();
@@ -798,26 +807,40 @@ public class VpnProfile implements Serializable, Cloneable {
}
+ private X509Certificate[] getKeyStoreCertificates(Context context) throws KeyChainException, InterruptedException {
+ PrivateKey privateKey = KeyChain.getPrivateKey(context, mAlias);
+ mPrivateKey = privateKey;
- class NoCertReturnedException extends Exception {
- public NoCertReturnedException(String msg) {
- super(msg);
- }
+
+ X509Certificate[] caChain = KeyChain.getCertificateChain(context, mAlias);
+ return caChain;
+ }
+
+ private X509Certificate[] getExtAppCertificates(Context context) throws KeyChainException {
+ if (mExternalAuthenticator == null || mAlias == null)
+ throw new KeyChainException("Alias or external auth provider name not set");
+ return ExtAuthHelper.getCertificateChain(context, mExternalAuthenticator, mAlias);
+ }
+
+ public String[] getExternalCertificates(Context context) {
+ return getExternalCertificates(context, 5);
}
- synchronized String[] getKeyStoreCertificates(Context context, int tries) {
+
+ synchronized String[] getExternalCertificates(Context context, int tries) {
// Force application context- KeyChain methods will block long enough that by the time they
// are finished and try to unbind, the original activity context might have been destroyed.
context = context.getApplicationContext();
try {
- PrivateKey privateKey = KeyChain.getPrivateKey(context, mAlias);
- mPrivateKey = privateKey;
-
String keystoreChain = null;
-
- X509Certificate[] caChain = KeyChain.getCertificateChain(context, mAlias);
+ X509Certificate caChain[];
+ if (mAuthenticationType == TYPE_EXTERNAL_APP) {
+ caChain = getExtAppCertificates(context);
+ } else {
+ caChain = getKeyStoreCertificates(context);
+ }
if (caChain == null)
throw new NoCertReturnedException("No certificate returned from Keystore");
@@ -900,14 +923,18 @@ public class VpnProfile implements Serializable, Cloneable {
} catch (InterruptedException e1) {
VpnStatus.logException(e1);
}
- return getKeyStoreCertificates(context, tries - 1);
+ return getExternalCertificates(context, tries - 1);
}
}
+ public int checkProfile(Context c) {
+ return checkProfile(c, doUseOpenVPN3(c));
+ }
+
//! Return an error if something is wrong
- public int checkProfile(Context context) {
- if (mAuthenticationType == TYPE_KEYSTORE || mAuthenticationType == TYPE_USERPASS_KEYSTORE) {
+ public int checkProfile(Context context, boolean useOpenVPN3) {
+ if (mAuthenticationType == TYPE_KEYSTORE || mAuthenticationType == TYPE_USERPASS_KEYSTORE || mAuthenticationType == TYPE_EXTERNAL_APP) {
if (mAlias == null)
return R.string.no_keystore_cert_selected;
} else if (mAuthenticationType == TYPE_CERTIFICATES || mAuthenticationType == TYPE_USERPASS_CERTIFICATES) {
@@ -944,21 +971,35 @@ public class VpnProfile implements Serializable, Cloneable {
boolean noRemoteEnabled = true;
- for (Connection c : mConnections)
+ for (Connection c : mConnections) {
if (c.mEnabled)
noRemoteEnabled = false;
+ }
if (noRemoteEnabled)
return R.string.remote_no_server_selected;
- if (doUseOpenVPN3(context)) {
+ if (useOpenVPN3) {
if (mAuthenticationType == TYPE_STATICKEYS) {
return R.string.openvpn3_nostatickeys;
}
if (mAuthenticationType == TYPE_PKCS12 || mAuthenticationType == TYPE_USERPASS_PKCS12) {
return R.string.openvpn3_pkcs12;
}
+ for (Connection conn : mConnections) {
+ if (conn.mProxyType == Connection.ProxyType.ORBOT || conn.mProxyType == Connection.ProxyType.SOCKS5)
+ return R.string.openvpn3_socksproxy;
+ }
}
+ for (Connection c : mConnections) {
+ if (c.mProxyType == Connection.ProxyType.ORBOT) {
+ if (usesExtraProxyOptions())
+ return R.string.error_orbot_and_proxy_options;
+ if (!OrbotHelper.checkTorReceier(context))
+ return R.string.no_orbotfound;
+ }
+ }
+
// Everything okay
return R.string.no_error_found;
@@ -1073,18 +1114,42 @@ public class VpnProfile implements Serializable, Cloneable {
}
public String getUUIDString() {
- return mUuid.toString();
+ return mUuid.toString().toLowerCase(Locale.ENGLISH);
}
public PrivateKey getKeystoreKey() {
return mPrivateKey;
}
- public String getSignedData(String b64data) {
- PrivateKey privkey = getKeystoreKey();
-
+ @Nullable
+ public String getSignedData(Context c, String b64data, boolean pkcs1padding) {
byte[] data = Base64.decode(b64data, Base64.DEFAULT);
+ byte[] signed_bytes;
+ if (mAuthenticationType == TYPE_EXTERNAL_APP)
+ signed_bytes = getExtAppSignedData(c, data);
+ else
+ signed_bytes = getKeyChainSignedData(data, pkcs1padding);
+ if (signed_bytes != null)
+ return Base64.encodeToString(signed_bytes, Base64.NO_WRAP);
+ else
+ return null;
+ }
+
+ private byte[] getExtAppSignedData(Context c, byte[] data) {
+ if (TextUtils.isEmpty(mExternalAuthenticator))
+ return null;
+ try {
+ return ExtAuthHelper.signData(c, mExternalAuthenticator, mAlias, data);
+ } catch (KeyChainException | InterruptedException e) {
+ VpnStatus.logError(R.string.error_extapp_sign, mExternalAuthenticator, e.getClass().toString(), e.getLocalizedMessage());
+ return null;
+ }
+ }
+
+ private byte[] getKeyChainSignedData(byte[] data, boolean pkcs1padding) {
+
+ PrivateKey privkey = getKeystoreKey();
// The Jelly Bean *evil* Hack
// 4.2 implements the RSA/ECB/PKCS1PADDING in the OpenSSLprovider
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) {
@@ -1109,15 +1174,17 @@ public class VpnProfile implements Serializable, Cloneable {
the public/private part in the TLS exchange
*/
Cipher signer;
- signer = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
+ if (pkcs1padding)
+ signer = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
+ else
+ signer = Cipher.getInstance("RSA/ECB/NoPadding");
signer.init(Cipher.ENCRYPT_MODE, privkey);
signed_bytes = signer.doFinal(data);
}
- return Base64.encodeToString(signed_bytes, Base64.NO_WRAP);
-
+ return signed_bytes;
} catch (NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException
| BadPaddingException | NoSuchPaddingException | SignatureException e) {
VpnStatus.logError(R.string.error_rsa_sign, e.getClass().toString(), e.getLocalizedMessage());
@@ -1125,7 +1192,7 @@ public class VpnProfile implements Serializable, Cloneable {
}
}
- private String processSignJellyBeans(PrivateKey privkey, byte[] data) {
+ private byte[] processSignJellyBeans(PrivateKey privkey, byte[] data) {
try {
Method getKey = privkey.getClass().getSuperclass().getDeclaredMethod("getOpenSSLKey");
getKey.setAccessible(true);
@@ -1143,8 +1210,7 @@ public class VpnProfile implements Serializable, Cloneable {
getPkeyContext.setAccessible(false);
// 112 with TLS 1.2 (172 back with 4.3), 36 with TLS 1.0
- byte[] signed_bytes = NativeUtils.rsasign(data, pkey);
- return Base64.encodeToString(signed_bytes, Base64.NO_WRAP);
+ return NativeUtils.rsasign(data, pkey);
} catch (NoSuchMethodException | InvalidKeyException | InvocationTargetException | IllegalAccessException | IllegalArgumentException e) {
VpnStatus.logError(R.string.error_rsa_sign, e.getClass().toString(), e.getLocalizedMessage());
@@ -1152,6 +1218,22 @@ public class VpnProfile implements Serializable, Cloneable {
}
}
+ private boolean usesExtraProxyOptions() {
+ if (mUseCustomConfig && mCustomConfigOptions != null && mCustomConfigOptions.contains("http-proxy-option "))
+ return true;
+ for (Connection c : mConnections)
+ if (c.usesExtraProxyOptions())
+ return true;
+
+ return false;
+ }
+
+ class NoCertReturnedException extends Exception {
+ public NoCertReturnedException(String msg) {
+ super(msg);
+ }
+ }
+
}
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<String, Vector<Vector<String>>> options = new HashMap<String, Vector<Vector<String>>>();
+ 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<String> connectionOptionsSet = new HashSet<>(Arrays.asList(connectionOptions));
+
+ private HashMap<String, Vector<Vector<String>>> options = new HashMap<>();
private HashMap<String, Vector<String>> meta = new HashMap<String, Vector<String>>();
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<String, String> 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<String> parseline(String line) throws ConfigParseError {
Vector<String> parameters = new Vector<String>();
@@ -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<String> 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<Vector<String>> tlsauthoptions = getAllOption("tls-auth", 1, 2);
@@ -426,7 +428,7 @@ public class ConfigParser {
np.mTLSAuthDirection = direction.get(1);
Vector<String> 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<String> tunmtu = getOption("mtu", 1, 1);
+ Vector<String> tunmtu = getOption("tun-mtu", 1, 1);
if (tunmtu != null) {
try {
@@ -478,7 +480,6 @@ public class ConfigParser {
}
-
Vector<String> mode = getOption("mode", 1, 1);
if (mode != null) {
if (!mode.get(1).equals("p2p"))
@@ -592,9 +593,9 @@ public class ConfigParser {
}
- Vector<String> x509usernamefield = getOption("x509-username-field", 1,1);
- if (x509usernamefield!=null) {
- np.mx509UsernameField = x509usernamefield.get(1);
+ Vector<String> 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<String> str) {
+ if (Build.VERSION.SDK_INT > 26)
+ return String.join(s, str);
+ else
+ return TextUtils.join(s, str);
+ }
+
private Pair<Connection, Connection[]> 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<Vector<String>> remotes = getAllOption("remote", 1, 3);
+ Vector<String> 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<String> 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<Vector<String>> option : options.values()) {
- conn.mCustomConfiguration += getOptionStrings(option);
+ // Parse remote config
+ Vector<Vector<String>> remotes = getAllOption("remote", 1, 3);
+
+
+ Vector <String> optionsToRemove = new Vector<>();
+ // Assume that we need custom options if connectionDefault are set or in the connection specific set
+ for (Map.Entry<String, Vector<Vector<String>>> 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<Vector<String>>();
@@ -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<String> 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<String> 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<Vector<String>> option: options.values())
+ {
+ for (Vector<String> 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<String> 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<Vector<String>> getAllOption(String option, int minarg, int maxarg) throws ConfigParseError {
Vector<Vector<String>> 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, "<http-proxy-user-pass>\n%s\n%s\n</http-proxy-user-pass>\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<ExternalAuthProvider> 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<ExternalAuthProvider>(c, android.R.layout.simple_spinner_item, android.R.id.text1, extProviders);
+ spinner.setAdapter(extAppAdapter);
+ if (selectedPos != -1)
+ spinner.setSelection(selectedPos);
+ }
+
+ static ArrayList<ExternalAuthProvider> getExternalAuthProviderList(Context c) {
+ Intent configureExtAuth = new Intent(ACTION_CERT_CONFIGURATION);
+
+ final PackageManager packageManager = c.getPackageManager();
+ List<ResolveInfo> configureList =
+ packageManager.queryIntentActivities(configureExtAuth, 0);
+
+ Intent serviceExtAuth = new Intent(ACTION_CERT_PROVIDER);
+
+ List<ResolveInfo> serviceList =
+ packageManager.queryIntentServices(serviceExtAuth, 0);
+
+
+ // For now only list those who appear in both lists
+
+ ArrayList<ExternalAuthProvider> providers = new ArrayList<ExternalAuthProvider>();
+
+ 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<X509Certificate> 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<X509Certificate> toCertificates(@NonNull byte[] bytes) {
+ final String BEGINCERT = "-----BEGIN CERTIFICATE-----";
+ try {
+ Vector<X509Certificate> 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<? extends X509Certificate>) 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<ExternalCertificateProvider> 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<ipAddress> {
+ static void assertTrue(boolean f)
+ {
+ if (!f)
+ throw new IllegalStateException();
+ }
+
+ static class IpAddress implements Comparable<IpAddress> {
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<ipAddress> mIpAddresses = new TreeSet<ipAddress>();
+ TreeSet<IpAddress> mIpAddresses = new TreeSet<IpAddress>();
- public Collection<ipAddress> getNetworks(boolean included) {
- Vector<ipAddress> ips = new Vector<ipAddress>();
- for (ipAddress ip : mIpAddresses) {
+ public Collection<IpAddress> getNetworks(boolean included) {
+ Vector<IpAddress> ips = new Vector<IpAddress>();
+ 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<ipAddress> generateIPList() {
+ TreeSet<IpAddress> generateIPList() {
- PriorityQueue<ipAddress> networks = new PriorityQueue<ipAddress>(mIpAddresses);
+ PriorityQueue<IpAddress> networks = new PriorityQueue<IpAddress>(mIpAddresses);
- TreeSet<ipAddress> ipsDone = new TreeSet<ipAddress>();
+ TreeSet<IpAddress> ipsDone = new TreeSet<IpAddress>();
- 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<ipAddress> getPositiveIPList() {
- TreeSet<ipAddress> ipsSorted = generateIPList();
+ Collection<IpAddress> getPositiveIPList() {
+ TreeSet<IpAddress> ipsSorted = generateIPList();
- Vector<ipAddress> ips = new Vector<ipAddress>();
- for (ipAddress ia : ipsSorted) {
+ Vector<IpAddress> ips = new Vector<IpAddress>();
+ 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<String> getLocalNetworks(Context c, boolean ipv6) {
+ Vector<String> 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<String> 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<ipAddress> positiveIPv4Routes = mRoutes.getPositiveIPList();
- Collection<ipAddress> positiveIPv6Routes = mRoutesv6.getPositiveIPList();
+ Collection<IpAddress> positiveIPv4Routes = mRoutes.getPositiveIPList();
+ Collection<IpAddress> 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<OpenVpnManagementThread> 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<OpenVpnManagementThread> 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<StatusCallback> 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<ResolveInfo> 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) {