From d77b9aefea75491b50f28a6880906ba9496979f2 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 21 Sep 2017 01:28:24 +0200 Subject: update ics-openvpn: update classes, manifest, resources and build script --- app/build.gradle | 25 +- .../java/se/leap/bitmaskclient/ProviderAPI.java | 2 +- app/src/main/AndroidManifest.xml | 6 +- app/src/main/java/de/blinkt/openvpn/LaunchVPN.java | 75 +++-- .../main/java/de/blinkt/openvpn/VpnProfile.java | 150 +++++++--- .../blinkt/openvpn/activities/DisconnectVPN.java | 52 ++-- .../java/de/blinkt/openvpn/api/APIVpnProfile.java | 60 ++++ .../main/java/de/blinkt/openvpn/core/CIDRIP.java | 4 +- .../java/de/blinkt/openvpn/core/ConfigParser.java | 89 +++++- .../java/de/blinkt/openvpn/core/Connection.java | 23 +- .../de/blinkt/openvpn/core/ConnectionStatus.java | 47 +++ .../blinkt/openvpn/core/DeviceStateReceiver.java | 17 +- .../blinkt/openvpn/core/ICSOpenVPNApplication.java | 61 ++-- .../de/blinkt/openvpn/core/LogFileHandler.java | 181 ++++++++---- .../main/java/de/blinkt/openvpn/core/LogItem.java | 6 +- .../java/de/blinkt/openvpn/core/NetworkSpace.java | 94 +++--- .../de/blinkt/openvpn/core/OpenVPNManagement.java | 2 +- .../de/blinkt/openvpn/core/OpenVPNService.java | 305 +++++++++++++------ .../blinkt/openvpn/core/OpenVPNStatusService.java | 232 +++++++++++++++ .../java/de/blinkt/openvpn/core/OpenVPNThread.java | 32 +- .../openvpn/core/OpenVpnManagementThread.java | 72 ++++- .../java/de/blinkt/openvpn/core/PasswordCache.java | 61 ++++ .../java/de/blinkt/openvpn/core/Preferences.java | 31 ++ .../de/blinkt/openvpn/core/ProfileManager.java | 114 +++++-- .../de/blinkt/openvpn/core/StatusListener.java | 109 +++++++ .../de/blinkt/openvpn/core/TrafficHistory.java | 243 +++++++++++++++ .../de/blinkt/openvpn/core/VPNLaunchHelper.java | 47 +-- .../java/de/blinkt/openvpn/core/VpnStatus.java | 328 +++++---------------- .../java/de/blinkt/openvpn/core/X509Utils.java | 9 +- .../de/blinkt/openvpn/fragments/LogFragment.java | 92 +++--- .../java/se/leap/bitmaskclient/VpnFragment.java | 6 +- .../main/java/se/leap/bitmaskclient/eip/EIP.java | 2 +- .../java/se/leap/bitmaskclient/eip/EipStatus.java | 24 +- .../res/layout-xlarge/eip_service_fragment.xml | 7 +- app/src/main/res/layout/eip_service_fragment.xml | 4 +- app/src/main/res/layout/vpnstatus.xml | 3 +- app/src/main/res/values-no/strings.xml | 2 - app/src/main/res/values-v21/refs.xml | 28 +- app/src/main/res/values/colours.xml | 3 + app/src/main/res/values/refs.xml | 29 +- app/src/main/res/values/untranslatable.xml | 3 +- .../de/blinkt/openvpn/core/OpenVPNThreadv3.java | 98 +++--- .../java/se/leap/bitmaskclient/ProviderAPI.java | 78 +++-- .../de/blinkt/openvpn/core/TestLogFileHandler.java | 257 ++++++++-------- ics-openvpn | 2 +- 45 files changed, 2176 insertions(+), 939 deletions(-) create mode 100644 app/src/main/java/de/blinkt/openvpn/api/APIVpnProfile.java create mode 100644 app/src/main/java/de/blinkt/openvpn/core/ConnectionStatus.java create mode 100644 app/src/main/java/de/blinkt/openvpn/core/OpenVPNStatusService.java create mode 100644 app/src/main/java/de/blinkt/openvpn/core/PasswordCache.java create mode 100644 app/src/main/java/de/blinkt/openvpn/core/Preferences.java create mode 100644 app/src/main/java/de/blinkt/openvpn/core/StatusListener.java create mode 100644 app/src/main/java/de/blinkt/openvpn/core/TrafficHistory.java diff --git a/app/build.gradle b/app/build.gradle index b6bb39ff..af44b9a2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,8 +1,8 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 23 - buildToolsVersion '25.0.2' + compileSdkVersion 26 + buildToolsVersion '26.0.0' ; signingConfigs { release { @@ -56,7 +56,8 @@ dependencies { compile 'com.google.code.gson:gson:2.4' compile 'org.thoughtcrime.ssl.pinning:AndroidPinning:1.0.0' compile 'mbanje.kurt:fabbutton:1.1.4' - compile 'com.android.support:support-annotations:23.2.1' + compile 'com.android.support:support-annotations:25.3.1' + compile 'com.android.support:support-v4:26.0.0-alpha1' } def processFileInplace(file, Closure processText) { @@ -84,6 +85,9 @@ task copyIcsOpenVPNClasses( type: Copy ) { include '**/logmenu.xml' include '**/core/**.java' include '**/activities/BaseActivity.java' + include '**/APIVpnProfile.java' + include '**/aidl/**/api/**.aidl' + include '**/aidl/**/core/**.aidl' includeEmptyDirs = false @@ -111,9 +115,11 @@ task copyIcsOpenVPNXml( type: Copy ) { include '**/colours.xml' include '**/logmenu.xml' include '**/white_rect.xml' + include '**/plurals.xml' includeEmptyDirs = false rename 'strings.xml', 'strings-icsopenvpn.xml' + rename 'plurals.xml', 'plurals-icsopenvpn.xml' filter { line -> line.replaceAll('.*name="app".*', '') } @@ -135,6 +141,7 @@ task copyIcsOpenVPNImages( type: Copy ) { } into '.' } +//TODO: avoid code duplication // thanks to http://pleac.sourceforge.net/pleac_groovy/fileaccess.html task removeDuplicatedStrings() { println "removeDuplicatedStrings" @@ -145,6 +152,18 @@ task removeDuplicatedStrings() { def ics_openvpn_strings_names = (new XmlParser()).parse(ics_openvpn_file) def current_file = it + ics_openvpn_strings_names.string.each { + processFileInplace(current_file) { text -> + text.replaceAll('.*name=\"' + it.attribute('name') + '\".*(\n)*.*string>.*\n+', '') + } + } + } + } else if (it.name.equals('plurals.xml')) { + def ics_openvpn_file = file(it.absolutePath.replace('plurals.xml', 'plurals-icsopenvpn.xml')) + if(ics_openvpn_file.exists()) { + def ics_openvpn_strings_names = (new XmlParser()).parse(ics_openvpn_file) + def current_file = it + ics_openvpn_strings_names.string.each { processFileInplace(current_file) { text -> text.replaceAll('.*name=\"' + it.attribute('name') + '\".*(\n)*.*string>.*\n+', '') diff --git a/app/src/insecure/java/se/leap/bitmaskclient/ProviderAPI.java b/app/src/insecure/java/se/leap/bitmaskclient/ProviderAPI.java index df827242..a1b1b383 100644 --- a/app/src/insecure/java/se/leap/bitmaskclient/ProviderAPI.java +++ b/app/src/insecure/java/se/leap/bitmaskclient/ProviderAPI.java @@ -20,7 +20,7 @@ import android.app.*; import android.content.*; import android.content.res.*; import android.os.*; -import android.util.*; +import android.util.Base64; import org.json.*; import org.thoughtcrime.ssl.pinning.util.*; diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7e0c9c0b..025c98f0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -28,7 +28,7 @@ + android:targetSdkVersion="26"/> + android:uiOptions="splitActionBarWhenNarrow" + android:launchMode="singleTask" + > diff --git a/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java b/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java index 16f986ae..0c3f20fb 100644 --- a/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java +++ b/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java @@ -7,15 +7,25 @@ package de.blinkt.openvpn; import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.R; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; import android.app.Activity; import android.app.AlertDialog; import android.content.ActivityNotFoundException; +import android.content.ComponentName; +import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; +import android.content.ServiceConnection; import android.content.SharedPreferences; import android.net.VpnService; +import android.os.Build; import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; import android.preference.PreferenceManager; import android.text.InputType; import android.text.TextUtils; @@ -28,10 +38,14 @@ import android.widget.EditText; import java.io.IOException; import de.blinkt.openvpn.activities.LogWindow; +import de.blinkt.openvpn.core.ConnectionStatus; +import de.blinkt.openvpn.core.IServiceStatus; +import de.blinkt.openvpn.core.OpenVPNStatusService; +import de.blinkt.openvpn.core.PasswordCache; +import de.blinkt.openvpn.core.Preferences; import de.blinkt.openvpn.core.ProfileManager; import de.blinkt.openvpn.core.VPNLaunchHelper; import de.blinkt.openvpn.core.VpnStatus; -import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus; /** * This Activity actually handles two stages of a launcher shortcut's life cycle. @@ -64,6 +78,7 @@ public class LaunchVPN extends Activity { public static final String EXTRA_NAME = "de.blinkt.openvpn.shortcutProfileName"; public static final String EXTRA_HIDELOG = "de.blinkt.openvpn.showNoLogWindow"; public static final String CLEARLOG = "clearlogconnect"; + public static final String EXTRA_TEMP_VPN_PROFILE = "se.leap.bitmask.tempVpnProfile"; private static final int START_VPN_PROFILE = 70; @@ -92,15 +107,17 @@ public class LaunchVPN extends Activity { if (Intent.ACTION_MAIN.equals(action)) { // Check if we need to clear the log - if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean(CLEARLOG, true)) + if (Preferences.getDefaultSharedPreferences(this).getBoolean(CLEARLOG, true)) VpnStatus.clearLog(); // we got called to be the starting point, most likely a shortcut String shortcutUUID = intent.getStringExtra(EXTRA_KEY); String shortcutName = intent.getStringExtra(EXTRA_NAME); mhideLog = intent.getBooleanExtra(EXTRA_HIDELOG, false); + VpnProfile profileToConnect = (VpnProfile) intent.getExtras().getSerializable(EXTRA_TEMP_VPN_PROFILE); + if (profileToConnect == null) + profileToConnect = ProfileManager.get(this, shortcutUUID); - VpnProfile profileToConnect = ProfileManager.get(this, shortcutUUID); if (shortcutName != null && profileToConnect == null) profileToConnect = ProfileManager.getInstance(this).getProfileByName(shortcutName); @@ -118,24 +135,28 @@ public class LaunchVPN extends Activity { @Override protected void onActivityResult (int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); + super.onActivityResult(requestCode, resultCode, data); - if(requestCode==START_VPN_PROFILE) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - boolean showLogWindow = prefs.getBoolean("showlogwindow", true); + if(requestCode==START_VPN_PROFILE) { + SharedPreferences prefs = Preferences.getDefaultSharedPreferences(this); + boolean showLogWindow = prefs.getBoolean("showlogwindow", true); + + if(!mhideLog && showLogWindow) + showLogWindow(); + ProfileManager.updateLRU(this, mSelectedProfile); + VPNLaunchHelper.startOpenVpn(mSelectedProfile, getBaseContext()); + finish(); - if(!mhideLog && showLogWindow) - showLogWindow(); + } else if (resultCode == Activity.RESULT_CANCELED) { + // User does not want us to start, so we just vanish + VpnStatus.updateStateString("USER_VPN_PERMISSION_CANCELLED", "", R.string.state_user_vpn_permission_cancelled, + ConnectionStatus.LEVEL_NOTCONNECTED); - VPNLaunchHelper.startOpenVpn(mSelectedProfile, getBaseContext()); - finish(); - } else if (resultCode == Activity.RESULT_CANCELED) { - // User does not want us to start, so we just vanish - VpnStatus.updateStateString("USER_VPN_PERMISSION_CANCELLED", "", R.string.state_user_vpn_permission_cancelled, - ConnectionStatus.LEVEL_NOTCONNECTED); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + VpnStatus.logError(R.string.nought_alwayson_warning); - finish(); - } + finish(); + } } void showLogWindow() { @@ -158,9 +179,27 @@ public class LaunchVPN extends Activity { } }); + d.setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + finish(); + } + }); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) + setOnDismissListener(d); d.show(); } + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) + private void setOnDismissListener(AlertDialog.Builder d) { + d.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + finish(); + } + }); + } + void launchVPN() { int vpnok = mSelectedProfile.checkProfile(this); if (vpnok != R.string.no_error_found) { @@ -170,7 +209,7 @@ public class LaunchVPN extends Activity { Intent intent = VpnService.prepare(this); // Check if we want to fix /dev/tun - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + SharedPreferences prefs = Preferences.getDefaultSharedPreferences(this); boolean usecm9fix = prefs.getBoolean("useCM9Fix", false); boolean loadTunModule = prefs.getBoolean("loadTunModule", false); diff --git a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java index 38d76f68..aa25da48 100644 --- a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java +++ b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java @@ -11,7 +11,6 @@ import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Build; @@ -54,6 +53,7 @@ import javax.crypto.NoSuchPaddingException; import de.blinkt.openvpn.core.Connection; import de.blinkt.openvpn.core.NativeUtils; import de.blinkt.openvpn.core.OpenVPNService; +import de.blinkt.openvpn.core.PasswordCache; import de.blinkt.openvpn.core.VPNLaunchHelper; import de.blinkt.openvpn.core.VpnStatus; import de.blinkt.openvpn.core.X509Utils; @@ -73,14 +73,10 @@ public class VpnProfile implements Serializable, Cloneable { private static final long serialVersionUID = 7085688938959334563L; public static final int MAXLOGLEVEL = 4; public static final int CURRENT_PROFILE_VERSION = 6; - public static final int DEFAULT_MSSFIX_SIZE = 1450; + 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 transient String mTransientPW = null; - public transient String mTransientPCKS12PW = null; - - public static final int TYPE_CERTIFICATES = 0; public static final int TYPE_PKCS12 = 1; public static final int TYPE_KEYSTORE = 2; @@ -94,6 +90,12 @@ public class VpnProfile implements Serializable, Cloneable { 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; + private static final int AUTH_RETRY_INTERACT = 3; // variable named wrong and should haven beeen transient // but needs to keep wrong name to guarante loading of old // profiles @@ -137,11 +139,14 @@ public class VpnProfile implements Serializable, Cloneable { public String mCustomRoutesv6 = ""; public String mKeyPassword = ""; public boolean mPersistTun = false; - public String mConnectRetryMax = "5"; - public String mConnectRetry = "5"; + public String mConnectRetryMax = "-1"; + public String mConnectRetry = "2"; + public String mConnectRetryMaxTime = "300"; public boolean mUserEditable = true; 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 @@ -159,15 +164,25 @@ public class VpnProfile implements Serializable, Cloneable { public String mCrlFilename; public String mProfileCreator; + 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; + /* Options no longer used in new profiles */ - public String mServerName = "openvpn.blinkt.de"; + public String mServerName = "openvpn.example.com"; public String mServerPort = "1194"; public boolean mUseUdp = true; + + public VpnProfile(String name) { mUuid = UUID.randomUUID(); mName = name; @@ -175,6 +190,7 @@ public class VpnProfile implements Serializable, Cloneable { mConnections = new Connection[1]; mConnections[0] = new Connection(); + mLastUsed = System.currentTimeMillis(); } public static String openVpnEscape(String unescaped) { @@ -192,6 +208,17 @@ public class VpnProfile implements Serializable, Cloneable { return '"' + escapedString + '"'; } + + @Override + public boolean equals(Object obj) { + if (obj instanceof VpnProfile) { + VpnProfile vpnProfile = (VpnProfile) obj; + return mUuid.equals(vpnProfile.mUuid); + } else { + return false; + } + } + public void clearDefaults() { mServerName = "unknown"; mUsePull = false; @@ -212,7 +239,7 @@ public class VpnProfile implements Serializable, Cloneable { } public String getName() { - if (mName == null) + if (TextUtils.isEmpty(mName)) return "No profile name"; return mName; } @@ -274,12 +301,13 @@ public class VpnProfile implements Serializable, Cloneable { if (!configForOvpn3) { cfg += String.format("setenv IV_GUI_VER %s \n", openVpnEscape(getVersionEnvString(context))); - String versionString = String.format("%d %s %s %s %s %s", Build.VERSION.SDK_INT, Build.VERSION.RELEASE, + String versionString = String.format(Locale.US, "%d %s %s %s %s %s", Build.VERSION.SDK_INT, Build.VERSION.RELEASE, NativeUtils.getNativeAPI(), Build.BRAND, Build.BOARD, Build.MODEL); cfg += String.format("setenv IV_PLAT_VER %s\n", openVpnEscape(versionString)); } cfg += "machine-readable-output\n"; + cfg += "allow-recursive-routing\n"; // Users are confused by warnings that are misleading... cfg += "ifconfig-nowarn\n"; @@ -299,19 +327,25 @@ public class VpnProfile implements Serializable, Cloneable { cfg += "verb " + MAXLOGLEVEL + "\n"; if (mConnectRetryMax == null) { - mConnectRetryMax = "5"; + mConnectRetryMax = "-1"; } if (!mConnectRetryMax.equals("-1")) cfg += "connect-retry-max " + mConnectRetryMax + "\n"; - if (mConnectRetry == null) - mConnectRetry = "5"; + if (TextUtils.isEmpty(mConnectRetry)) + mConnectRetry = "2"; + + if (TextUtils.isEmpty(mConnectRetryMaxTime)) + mConnectRetryMaxTime = "300"; - if (!mIsOpenVPN22 || !mUseUdp) + if (!mIsOpenVPN22) + cfg += "connect-retry " + mConnectRetry + " " + mConnectRetryMaxTime + "\n"; + else if (mIsOpenVPN22 && mUseUdp) cfg += "connect-retry " + mConnectRetry + "\n"; + cfg += "resolv-retry 60\n"; @@ -384,6 +418,12 @@ public class VpnProfile implements Serializable, Cloneable { cfg += insertFileData("ca", mCaFilename); } + if (isUserPWAuth()) + { + if (mAuthenticationType == AUTH_RETRY_NOINTERACT) + cfg += "auth-retry nointeract"; + } + if (!TextUtils.isEmpty(mCrlFilename)) cfg += insertFileData("crl-verify", mCrlFilename); @@ -392,12 +432,16 @@ public class VpnProfile implements Serializable, Cloneable { } if (mUseTLSAuth) { + boolean useTlsCrypt = mTLSAuthDirection.equals("tls-crypt"); + if (mAuthenticationType == TYPE_STATICKEYS) cfg += insertFileData("secret", mTLSAuthFilename); + else if (useTlsCrypt) + cfg += insertFileData("tls-crypt", mTLSAuthFilename); else cfg += insertFileData("tls-auth", mTLSAuthFilename); - if (!TextUtils.isEmpty(mTLSAuthDirection)) { + if (!TextUtils.isEmpty(mTLSAuthDirection) && !useTlsCrypt) { cfg += "key-direction "; cfg += mTLSAuthDirection; cfg += "\n"; @@ -409,8 +453,12 @@ public class VpnProfile implements Serializable, Cloneable { if (!TextUtils.isEmpty(mIPv4Address)) cfg += "ifconfig " + cidrToIPAndNetmask(mIPv4Address) + "\n"; - if (!TextUtils.isEmpty(mIPv6Address)) - cfg += "ifconfig-ipv6 " + mIPv6Address + "\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"; + } + } if (mUsePull && mRoutenopull) @@ -441,22 +489,33 @@ public class VpnProfile implements Serializable, Cloneable { cfg += routes; if (mOverrideDNS || !mUsePull) { - if (!TextUtils.isEmpty(mDNS1)) - cfg += "dhcp-option DNS " + mDNS1 + "\n"; - if (!TextUtils.isEmpty(mDNS2)) - cfg += "dhcp-option DNS " + mDNS2 + "\n"; - if (!TextUtils.isEmpty(mSearchDomain)) + if (!TextUtils.isEmpty(mDNS1)) { + if (mDNS1.contains(":")) + cfg += "dhcp-option DNS6 " + mDNS1 + "\n"; + else + cfg += "dhcp-option DNS " + mDNS1 + "\n"; + } if (!TextUtils.isEmpty(mDNS2)) { + if (mDNS2.contains(":")) + cfg += "dhcp-option DNS6 " + mDNS2 + "\n"; + else + cfg += "dhcp-option DNS " + mDNS2 + "\n"; + } if (!TextUtils.isEmpty(mSearchDomain)) cfg += "dhcp-option DOMAIN " + mSearchDomain + "\n"; } if (mMssFix != 0) { if (mMssFix != 1450) { - cfg += String.format("mssfix %d\n", mMssFix, Locale.US); + cfg += String.format(Locale.US, "mssfix %d\n", mMssFix); } else cfg += "mssfix\n"; } + if (mTunMtu >= 48 && mTunMtu != 1500) + { + cfg+= String.format(Locale.US, "tun-mtu %d\n", mTunMtu); + } + if (mNobind) cfg += "nobind\n"; @@ -465,7 +524,7 @@ public class VpnProfile implements Serializable, Cloneable { if (mAuthenticationType != TYPE_STATICKEYS) { if (mCheckRemoteCN) { if (mRemoteCN == null || mRemoteCN.equals("")) - cfg += "verify-x509-name " + mConnections[0].mServerName + " name\n"; + cfg += "verify-x509-name " + openVpnEscape(mConnections[0].mServerName) + " name\n"; else switch (mX509AuthType) { @@ -488,6 +547,8 @@ public class VpnProfile implements Serializable, Cloneable { cfg += "verify-x509-name " + openVpnEscape(mRemoteCN) + "\n"; break; } + if (!TextUtils.isEmpty(mx509UsernameField)) + cfg += "x509-username-field " + openVpnEscape(mx509UsernameField) + "\n"; } if (mExpectTLSCert) cfg += "remote-cert-tls server\n"; @@ -659,7 +720,7 @@ public class VpnProfile implements Serializable, Cloneable { Intent intent = new Intent(context, OpenVPNService.class); intent.putExtra(prefix + ".profileUUID", mUuid.toString()); - + intent.putExtra(prefix + ".profileVersion", mVersion); return intent; } @@ -730,6 +791,10 @@ public class VpnProfile implements Serializable, Cloneable { } } + public void pwDidFail(Context c) { + + } + class NoCertReturnedException extends Exception { public NoCertReturnedException(String msg) { @@ -738,6 +803,10 @@ public class VpnProfile implements Serializable, Cloneable { } synchronized String[] getKeyStoreCertificates(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; @@ -838,8 +907,14 @@ public class VpnProfile implements Serializable, Cloneable { if (mAuthenticationType == TYPE_KEYSTORE || mAuthenticationType == TYPE_USERPASS_KEYSTORE) { if (mAlias == null) return R.string.no_keystore_cert_selected; + } else if (mAuthenticationType == TYPE_CERTIFICATES || mAuthenticationType == TYPE_USERPASS_CERTIFICATES){ + if (TextUtils.isEmpty(mCaFilename)) + return R.string.no_ca_cert_selected; } + if (mCheckRemoteCN && mX509AuthType==X509_VERIFY_TLSREMOTE) + return R.string.deprecated_tls_remote; + if (!mUsePull || mAuthenticationType == TYPE_STATICKEYS) { if (mIPv4Address == null || cidrToIPAndNetmask(mIPv4Address) == null) return R.string.ipv4_format_error; @@ -881,10 +956,9 @@ public class VpnProfile implements Serializable, Cloneable { //! Openvpn asks for a "Private Key", this should be pkcs12 key // public String getPasswordPrivateKey() { - if (mTransientPCKS12PW != null) { - String pwcopy = mTransientPCKS12PW; - mTransientPCKS12PW = null; - return pwcopy; + String cachedPw = PasswordCache.getPKCS12orCertificatePassword(mUuid, true); + if (cachedPw != null) { + return cachedPw; } switch (mAuthenticationType) { case TYPE_PKCS12: @@ -949,33 +1023,32 @@ public class VpnProfile implements Serializable, Cloneable { return false; } - public int needUserPWInput(boolean ignoreTransient) { + public int needUserPWInput(String transientCertOrPkcs12PW, String mTransientAuthPW) { if ((mAuthenticationType == TYPE_PKCS12 || mAuthenticationType == TYPE_USERPASS_PKCS12) && (mPKCS12Password == null || mPKCS12Password.equals(""))) { - if (ignoreTransient || mTransientPCKS12PW == null) + if (transientCertOrPkcs12PW == null) return R.string.pkcs12_file_encryption_key; } if (mAuthenticationType == TYPE_CERTIFICATES || mAuthenticationType == TYPE_USERPASS_CERTIFICATES) { if (requireTLSKeyPassword() && TextUtils.isEmpty(mKeyPassword)) - if (ignoreTransient || mTransientPCKS12PW == null) { + if (transientCertOrPkcs12PW == null) { return R.string.private_key_password; } } if (isUserPWAuth() && (TextUtils.isEmpty(mUsername) || - (TextUtils.isEmpty(mPassword) && (mTransientPW == null || ignoreTransient)))) { + (TextUtils.isEmpty(mPassword) && mTransientAuthPW == null))) { return R.string.password; } return 0; } public String getPasswordAuth() { - if (mTransientPW != null) { - String pwcopy = mTransientPW; - mTransientPW = null; - return pwcopy; + String cachedPw = PasswordCache.getAuthPassword(mUuid, true); + if (cachedPw != null) { + return cachedPw; } else { return mPassword; } @@ -1008,7 +1081,6 @@ public class VpnProfile implements Serializable, Cloneable { try { - /* ECB is perfectly fine in this special case, since we are using it for the public/private part in the TLS exchange */ diff --git a/app/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java b/app/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java index d25bccad..068821f5 100644 --- a/app/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java +++ b/app/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java @@ -7,33 +7,40 @@ package de.blinkt.openvpn.activities; import android.app.Activity; import android.app.AlertDialog; -import android.content.*; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.ServiceConnection; import android.os.IBinder; +import android.os.RemoteException; +import de.blinkt.openvpn.LaunchVPN; import se.leap.bitmaskclient.R; +import de.blinkt.openvpn.core.IOpenVPNServiceInternal; import de.blinkt.openvpn.core.OpenVPNService; import de.blinkt.openvpn.core.ProfileManager; +import de.blinkt.openvpn.core.VpnStatus; /** * Created by arne on 13.10.13. */ public class DisconnectVPN extends Activity implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener { - protected OpenVPNService mService; - + private IOpenVPNServiceInternal mService; private ServiceConnection mConnection = new ServiceConnection() { + @Override public void onServiceConnected(ComponentName className, IBinder service) { - // We've bound to LocalService, cast the IBinder and get LocalService instance - OpenVPNService.LocalBinder binder = (OpenVPNService.LocalBinder) service; - mService = binder.getService(); + + mService = IOpenVPNServiceInternal.Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName arg0) { - mService =null; + mService = null; } }; @@ -53,24 +60,13 @@ public class DisconnectVPN extends Activity implements DialogInterface.OnClickLi unbindService(mConnection); } - // if (getIntent() !=null && OpenVpnService.DISCONNECT_VPN.equals(getIntent().getAction())) - - // setIntent(null); - - /* - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - setIntent(intent); - } - */ - private void showDisconnectDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.title_cancel); builder.setMessage(R.string.cancel_connection_query); - builder.setNegativeButton(android.R.string.no, this); - builder.setPositiveButton(android.R.string.yes,this); + builder.setNegativeButton(android.R.string.cancel, this); + builder.setPositiveButton(R.string.cancel_connection, this); + builder.setNeutralButton(R.string.reconnect, this); builder.setOnCancelListener(this); builder.show(); @@ -80,8 +76,18 @@ public class DisconnectVPN extends Activity implements DialogInterface.OnClickLi public void onClick(DialogInterface dialog, int which) { if (which == DialogInterface.BUTTON_POSITIVE) { ProfileManager.setConntectedVpnProfileDisconnected(this); - if (mService != null && mService.getManagement() != null) - mService.getManagement().stopVPN(false); + if (mService != null) { + try { + mService.stopVPN(false); + } catch (RemoteException e) { + VpnStatus.logException(e); + } + } + } else if (which == DialogInterface.BUTTON_NEUTRAL) { + Intent intent = new Intent(this, LaunchVPN.class); + intent.putExtra(LaunchVPN.EXTRA_KEY, VpnStatus.getLastConnectedVPNProfile()); + intent.setAction(Intent.ACTION_MAIN); + startActivity(intent); } finish(); } diff --git a/app/src/main/java/de/blinkt/openvpn/api/APIVpnProfile.java b/app/src/main/java/de/blinkt/openvpn/api/APIVpnProfile.java new file mode 100644 index 00000000..adc7f8b7 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/api/APIVpnProfile.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2012-2016 Arne Schwabe + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt + */ + +package de.blinkt.openvpn.api; + +import android.os.Parcel; +import android.os.Parcelable; + +public class APIVpnProfile implements Parcelable { + + public final String mUUID; + public final String mName; + public final boolean mUserEditable; + //public final String mProfileCreator; + + public APIVpnProfile(Parcel in) { + mUUID = in.readString(); + mName = in.readString(); + mUserEditable = in.readInt() != 0; + //mProfileCreator = in.readString(); + } + + public APIVpnProfile(String uuidString, String name, boolean userEditable, String profileCreator) { + mUUID = uuidString; + mName = name; + mUserEditable = userEditable; + //mProfileCreator = profileCreator; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mUUID); + dest.writeString(mName); + if (mUserEditable) + dest.writeInt(0); + else + dest.writeInt(1); + //dest.writeString(mProfileCreator); + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public APIVpnProfile createFromParcel(Parcel in) { + return new APIVpnProfile(in); + } + + public APIVpnProfile[] newArray(int size) { + return new APIVpnProfile[size]; + } + }; + + +} 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 07f2152f..799c68c9 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/CIDRIP.java +++ b/app/src/main/java/de/blinkt/openvpn/core/CIDRIP.java @@ -47,9 +47,9 @@ class CIDRIP { public boolean normalise() { long ip = getInt(mIp); - long newip = ip & (0xffffffffl << (32 - len)); + long newip = ip & (0xffffffffL << (32 - len)); if (newip != ip) { - mIp = String.format("%d.%d.%d.%d", (newip & 0xff000000) >> 24, (newip & 0xff0000) >> 16, (newip & 0xff00) >> 8, newip & 0xff); + mIp = String.format(Locale.US,"%d.%d.%d.%d", (newip & 0xff000000) >> 24, (newip & 0xff0000) >> 16, (newip & 0xff00) >> 8, newip & 0xff); return true; } else { return false; 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 d14e643e..74afd61e 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java +++ b/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java @@ -6,7 +6,7 @@ package de.blinkt.openvpn.core; import android.text.TextUtils; -import android.util.Pair; +import android.support.v4.util.Pair; import java.io.BufferedReader; import java.io.IOException; @@ -119,6 +119,9 @@ public class ConfigParser { } } while (true); + if(inlinefile.endsWith("\n")) + inlinefile = inlinefile.substring(0, inlinefile.length()-1); + args.clear(); args.add(argname); args.add(inlinefile); @@ -251,10 +254,12 @@ public class ConfigParser { "route-up", "route-pre-down", "auth-user-pass-verify", + "block-outside-dns", "dhcp-release", "dhcp-renew", "dh", "group", + "allow-recursive-routing", "ip-win32", "management-hold", "management", @@ -273,6 +278,7 @@ public class ConfigParser { "plugin", "machine-readable-output", "persist-key", + "push", "register-dns", "route-delay", "route-gateway", @@ -322,7 +328,6 @@ public class ConfigParser { "socks-proxy", "socks-proxy-retry", "explicit-exit-notify", - "mssfix" }; @@ -394,7 +399,7 @@ public class ConfigParser { np.mCustomRoutesv6 = customIPv6Routes; } - Vector routeNoPull = getOption("route-nopull", 1, 1); + Vector routeNoPull = getOption("route-nopull", 0, 0); if (routeNoPull!=null) np.mRoutenopull=true; @@ -417,15 +422,21 @@ public class ConfigParser { if (direction != null) np.mTLSAuthDirection = direction.get(1); - Vector> defgw = getAllOption("redirect-gateway", 0, 5); + Vector tlscrypt = getOption("tls-crypt", 1, 1); + if (tlscrypt!=null) { + np.mUseTLSAuth = true; + np.mTLSAuthFilename = tlscrypt.get(1); + np.mTLSAuthDirection = "tls-crypt"; + } + + Vector> defgw = getAllOption("redirect-gateway", 0, 7); if (defgw != null) { - np.mUseDefaultRoute = true; - checkRedirectParameters(np, defgw); + checkRedirectParameters(np, defgw, true); } Vector> redirectPrivate = getAllOption("redirect-private", 0, 5); if (redirectPrivate != null) { - checkRedirectParameters(np, redirectPrivate); + checkRedirectParameters(np, redirectPrivate, false); } Vector dev = getOption("dev", 1, 1); Vector devtype = getOption("dev-type", 1, 1); @@ -448,11 +459,23 @@ public class ConfigParser { throw new ConfigParseError("Argument to --mssfix has to be an integer"); } } else { - np.mMssFix = VpnProfile.DEFAULT_MSSFIX_SIZE; + np.mMssFix = 1450; // OpenVPN default size } } + Vector tunmtu = getOption("mtu", 1, 1); + + if (tunmtu != null) { + try { + np.mTunMtu = Integer.parseInt(tunmtu.get(1)); + } catch (NumberFormatException e) { + throw new ConfigParseError("Argument to --tun-mtu has to be an integer"); + } + } + + + Vector mode = getOption("mode", 1, 1); if (mode != null) { if (!mode.get(1).equals("p2p")) @@ -554,16 +577,23 @@ public class ConfigParser { if (verifyx509name.size() > 2) { if (verifyx509name.get(2).equals("name")) np.mX509AuthType = VpnProfile.X509_VERIFY_TLSREMOTE_RDN; + else if (verifyx509name.get(2).equals("subject")) + np.mX509AuthType = VpnProfile.X509_VERIFY_TLSREMOTE_DN; else if (verifyx509name.get(2).equals("name-prefix")) np.mX509AuthType = VpnProfile.X509_VERIFY_TLSREMOTE_RDN_PREFIX; else - throw new ConfigParseError("Unknown parameter to x509-verify-name: " + verifyx509name.get(2)); + throw new ConfigParseError("Unknown parameter to verify-x509-name: " + verifyx509name.get(2)); } else { np.mX509AuthType = VpnProfile.X509_VERIFY_TLSREMOTE_DN; } } + Vector x509usernamefield = getOption("x509-username-field", 1,1); + if (x509usernamefield!=null) { + np.mx509UsernameField = x509usernamefield.get(1); + } + Vector verb = getOption("verb", 1, 1); if (verb != null) { @@ -580,9 +610,12 @@ public class ConfigParser { if (getOption("push-peer-info", 0, 0) != null) np.mPushPeerInfo = true; - Vector connectretry = getOption("connect-retry", 1, 1); - if (connectretry != null) + Vector connectretry = getOption("connect-retry", 1, 2); + if (connectretry != null) { np.mConnectRetry = connectretry.get(1); + if (connectretry.size() > 2) + np.mConnectRetryMaxTime = connectretry.get(2); + } Vector connectretrymax = getOption("connect-retry-max", 1, 1); if (connectretrymax != null) @@ -613,6 +646,19 @@ public class ConfigParser { } } + Vector authretry = getOption("auth-retry", 1, 1); + if (authretry != null) { + if (authretry.get(1).equals("none")) + np.mAuthRetry = VpnProfile.AUTH_RETRY_NONE_FORGET; + else if (authretry.get(1).equals("nointeract")) + np.mAuthRetry = VpnProfile.AUTH_RETRY_NOINTERACT; + else if (authretry.get(1).equals("interact")) + np.mAuthRetry = VpnProfile.AUTH_RETRY_NOINTERACT; + else + throw new ConfigParseError("Unknown parameter to auth-retry: " + authretry.get(2)); + } + + Vector crlfile = getOption("crl-verify", 1, 2); if (crlfile != null) { // If the 'dir' parameter is present just add it as custom option .. @@ -776,22 +822,34 @@ public class ConfigParser { } - private void checkRedirectParameters(VpnProfile np, Vector> defgw) { + private void checkRedirectParameters(VpnProfile np, Vector> defgw, boolean defaultRoute) { + + boolean noIpv4 = false; + if (defaultRoute) + for (Vector redirect : defgw) for (int i = 1; i < redirect.size(); i++) { if (redirect.get(i).equals("block-local")) np.mAllowLocalLAN = false; else if (redirect.get(i).equals("unblock-local")) np.mAllowLocalLAN = true; + else if (redirect.get(i).equals("!ipv4")) + noIpv4=true; + else if (redirect.get(i).equals("ipv6")) + np.mUseDefaultRoutev6=true; } + if (defaultRoute && !noIpv4) + np.mUseDefaultRoute=true; } private boolean isUdpProto(String proto) throws ConfigParseError { boolean isudp; - if (proto.equals("udp") || proto.equals("udp6")) + if (proto.equals("udp") || proto.equals("udp4") || proto.equals("udp6")) isudp = true; else if (proto.equals("tcp-client") || proto.equals("tcp") || + proto.equals("tcp4") || + proto.endsWith("tcp4-client") || proto.equals("tcp6") || proto.endsWith("tcp6-client")) isudp = false; @@ -858,10 +916,9 @@ public class ConfigParser { for (Vector optionsline : option) { if (!ignoreThisOption(optionsline)) { // Check if option had been inlined and inline again - if (optionsline.size() == 2 && "extra-certs".equals(optionsline.get(0)) ) { + if (optionsline.size() == 2 && + ("extra-certs".equals(optionsline.get(0)) || "http-proxy-user-pass".equals(optionsline.get(0)))) { custom += VpnProfile.insertFileData(optionsline.get(0), optionsline.get(1)); - - } else { for (String arg : optionsline) custom += VpnProfile.openVpnEscape(arg) + " "; 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 3455450b..ff15daec 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/Connection.java +++ b/app/src/main/java/de/blinkt/openvpn/core/Connection.java @@ -8,21 +8,23 @@ package de.blinkt.openvpn.core; import android.text.TextUtils; import java.io.Serializable; +import java.util.Locale; public class Connection implements Serializable, Cloneable { - public String mServerName = "openvpn.blinkt.de"; + public String mServerName = "openvpn.example.com"; public String mServerPort = "1194"; public boolean mUseUdp = true; - public String mCustomConfiguration=""; - public boolean mUseCustomConfig=false; - public boolean mEnabled=true; + public String mCustomConfiguration = ""; + public boolean mUseCustomConfig = false; + public boolean mEnabled = true; public int mConnectTimeout = 0; + public static final int CONNECTION_DEFAULT_TIMEOUT = 120; private static final long serialVersionUID = 92031902903829089L; public String getConnectionBlock() { - String cfg=""; + String cfg = ""; // Server Address cfg += "remote "; @@ -34,8 +36,8 @@ public class Connection implements Serializable, Cloneable { else cfg += " tcp-client\n"; - if (mConnectTimeout!=0) - cfg += String.format(" connect-timeout %d\n" , mConnectTimeout); + if (mConnectTimeout != 0) + cfg += String.format(Locale.US, " connect-timeout %d\n", mConnectTimeout); if (!TextUtils.isEmpty(mCustomConfiguration) && mUseCustomConfig) { @@ -53,4 +55,11 @@ public class Connection implements Serializable, Cloneable { public boolean isOnlyRemote() { return TextUtils.isEmpty(mCustomConfiguration) || !mUseCustomConfig; } + + public int getTimeout() { + if (mConnectTimeout <= 0) + return CONNECTION_DEFAULT_TIMEOUT; + else + return mConnectTimeout; + } } diff --git a/app/src/main/java/de/blinkt/openvpn/core/ConnectionStatus.java b/app/src/main/java/de/blinkt/openvpn/core/ConnectionStatus.java new file mode 100644 index 00000000..03d842e3 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/core/ConnectionStatus.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2012-2016 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.os.Parcel; +import android.os.Parcelable; + +/** + * Created by arne on 08.11.16. + */ +public enum ConnectionStatus implements Parcelable { + LEVEL_CONNECTED, + LEVEL_VPNPAUSED, + LEVEL_CONNECTING_SERVER_REPLIED, + LEVEL_CONNECTING_NO_SERVER_REPLY_YET, + LEVEL_NONETWORK, + LEVEL_NOTCONNECTED, + LEVEL_START, + LEVEL_AUTH_FAILED, + LEVEL_WAITING_FOR_USER_INPUT, + UNKNOWN_LEVEL; + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(ordinal()); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator CREATOR = new Creator() { + @Override + public ConnectionStatus createFromParcel(Parcel in) { + return ConnectionStatus.values()[in.readInt()]; + } + + @Override + public ConnectionStatus[] newArray(int size) { + return new ConnectionStatus[size]; + } + }; +} diff --git a/app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java b/app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java index 40684af3..c396f181 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java +++ b/app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java @@ -20,6 +20,7 @@ import de.blinkt.openvpn.core.VpnStatus.ByteCountListener; import java.util.LinkedList; import java.util.Objects; +import java.util.StringTokenizer; import static de.blinkt.openvpn.core.OpenVPNManagement.pauseReason; @@ -64,13 +65,13 @@ public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountL return shouldBeConnected(); } - enum connectState { + private enum connectState { SHOULDBECONNECTED, PENDINGDISCONNECT, DISCONNECTED } - static class Datapoint { + private static class Datapoint { private Datapoint(long t, long d) { timestamp = t; data = d; @@ -80,7 +81,7 @@ public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountL long data; } - LinkedList trafficdata = new LinkedList(); + private LinkedList trafficdata = new LinkedList<>(); @Override @@ -102,7 +103,7 @@ public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountL if (windowtraffic < TRAFFIC_LIMIT) { screen = connectState.DISCONNECTED; VpnStatus.logInfo(R.string.screenoff_pause, - OpenVPNService.humanReadableByteCount(TRAFFIC_LIMIT, false), TRAFFIC_WINDOW); + "64 kB", TRAFFIC_WINDOW); mManagement.pause(getPauseReason()); } @@ -135,7 +136,7 @@ public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountL @Override public void onReceive(Context context, Intent intent) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + SharedPreferences prefs = Preferences.getDefaultSharedPreferences(context); if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) { @@ -173,15 +174,15 @@ public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountL private void fillTrafficData() { trafficdata.add(new Datapoint(System.currentTimeMillis(), TRAFFIC_LIMIT)); } + public static boolean equalsObj(Object a, Object b) { return (a == null) ? (b == null) : a.equals(b); } - public void networkStateChange(Context context) { NetworkInfo networkInfo = getCurrentNetworkInfo(context); - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + SharedPreferences prefs = Preferences.getDefaultSharedPreferences(context); boolean sendusr1 = prefs.getBoolean("netchangereconnect", true); @@ -261,6 +262,8 @@ public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountL if (!netstatestring.equals(lastStateMsg)) VpnStatus.logInfo(R.string.netstatus, netstatestring); + VpnStatus.logDebug(String.format("Debug state info: %s, pause: %s, shouldbeconnected: %s, network: %s ", + netstatestring, getPauseReason(), shouldBeConnected(), network)); lastStateMsg = netstatestring; } 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 db3ae751..e7019f42 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java +++ b/app/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java @@ -4,7 +4,14 @@ */ package de.blinkt.openvpn.core; + +import android.annotation.TargetApi; import android.app.Application; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.graphics.Color; +import android.os.Build; /* import org.acra.ACRA; @@ -12,32 +19,50 @@ import org.acra.ReportingInteractionMode; import org.acra.annotation.ReportsCrashes; */ -import se.leap.bitmaskclient.BuildConfig; import se.leap.bitmaskclient.R; -import de.blinkt.openvpn.core.PRNGFixes; -/* -@ReportsCrashes( - formKey = "", - formUri = "http://reports.blinkt.de/report-icsopenvpn", - reportType = org.acra.sender.HttpSender.Type.JSON, - httpMethod = org.acra.sender.HttpSender.Method.PUT, - formUriBasicAuthLogin="report-icsopenvpn", - formUriBasicAuthPassword="Tohd4neiF9Ai!!!!111eleven", - mode = ReportingInteractionMode.TOAST, - resToastText = R.string.crash_toast_text -) -*/ public class ICSOpenVPNApplication extends Application { + private StatusListener mStatus; + @Override public void onCreate() { super.onCreate(); PRNGFixes.apply(); - if (BuildConfig.DEBUG) { - //ACRA.init(this); - } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + + createNotificationChannels(); + mStatus = new StatusListener(); + mStatus.init(getApplicationContext()); + + } + + @TargetApi(Build.VERSION_CODES.O) + private void createNotificationChannels() { + NotificationManager mNotificationManager = + (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + + // Background message + CharSequence name = getString(R.string.channel_name_background); + NotificationChannel mChannel = new NotificationChannel(OpenVPNService.NOTIFICATION_CHANNEL_BG_ID, + name, NotificationManager.IMPORTANCE_MIN); + + mChannel.setDescription(getString(R.string.channel_description_background)); + mChannel.enableLights(false); + + mChannel.setLightColor(Color.DKGRAY); + mNotificationManager.createNotificationChannel(mChannel); + + // Connection status change messages + + name = getString(R.string.channel_name_status); + mChannel = new NotificationChannel(OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID, + name, NotificationManager.IMPORTANCE_DEFAULT); + + mChannel.setDescription(getString(R.string.channel_description_status)); + mChannel.enableLights(true); - VpnStatus.initLogCache(getApplicationContext().getCacheDir()); + mChannel.setLightColor(Color.BLUE); + mNotificationManager.createNotificationChannel(mChannel); } } 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 288c7934..57d1fb22 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/LogFileHandler.java +++ b/app/src/main/java/de/blinkt/openvpn/core/LogFileHandler.java @@ -8,7 +8,6 @@ package de.blinkt.openvpn.core; import android.os.Handler; import android.os.Looper; import android.os.Message; -import android.os.Parcel; import java.io.BufferedInputStream; import java.io.File; @@ -16,6 +15,10 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.util.Locale; @@ -29,7 +32,8 @@ class LogFileHandler extends Handler { static final int FLUSH_TO_DISK = 101; static final int LOG_INIT = 102; public static final int LOG_MESSAGE = 103; - private static FileOutputStream mLogFile; + public static final int MAGIC_BYTE = 0x55; + protected OutputStream mLogFile; public static final String LOGFILE_NAME = "logcache.dat"; @@ -47,20 +51,20 @@ class LogFileHandler extends Handler { throw new RuntimeException("mLogFile not null"); readLogCache((File) msg.obj); openLogFile((File) msg.obj); - } else if (msg.what == LOG_MESSAGE && msg.obj instanceof VpnStatus.LogItem) { + } else if (msg.what == LOG_MESSAGE && msg.obj instanceof LogItem) { // Ignore log messages if not yet initialized if (mLogFile == null) return; - writeLogItemToDisk((VpnStatus.LogItem) msg.obj); + writeLogItemToDisk((LogItem) msg.obj); } else if (msg.what == TRIM_LOG_FILE) { trimLogFile(); - for (VpnStatus.LogItem li : VpnStatus.getlogbuffer()) + for (LogItem li : VpnStatus.getlogbuffer()) writeLogItemToDisk(li); } else if (msg.what == FLUSH_TO_DISK) { flushToDisk(); } - } catch (IOException e) { + } catch (IOException | BufferOverflowException e) { e.printStackTrace(); VpnStatus.logError("Error during log cache: " + msg.what); VpnStatus.logException(e); @@ -72,95 +76,160 @@ class LogFileHandler extends Handler { mLogFile.flush(); } - private static void trimLogFile() { + private void trimLogFile() { try { mLogFile.flush(); - mLogFile.getChannel().truncate(0); + ((FileOutputStream) mLogFile).getChannel().truncate(0); } catch (IOException e) { e.printStackTrace(); } } - private void writeLogItemToDisk(VpnStatus.LogItem li) throws IOException { - Parcel p = Parcel.obtain(); - li.writeToParcel(p, 0); + private void writeLogItemToDisk(LogItem li) throws IOException { + // We do not really care if the log cache breaks between Android upgrades, // write binary format to disc - byte[] liBytes = p.marshall(); - byte[] lenBytes = ByteBuffer.allocate(4).putInt(liBytes.length).array(); - mLogFile.write(lenBytes); - mLogFile.write(liBytes); - p.recycle(); + byte[] liBytes = li.getMarschaledBytes(); + + writeEscapedBytes(liBytes); + } + + public void writeEscapedBytes(byte[] bytes) throws IOException { + int magic = 0; + for (byte b : bytes) + if (b == MAGIC_BYTE || b == MAGIC_BYTE + 1) + magic++; + + byte eBytes[] = new byte[bytes.length + magic]; + + int i = 0; + for (byte b : bytes) { + if (b == MAGIC_BYTE || b == MAGIC_BYTE + 1) { + eBytes[i++] = MAGIC_BYTE + 1; + eBytes[i++] = (byte) (b - MAGIC_BYTE); + } else { + eBytes[i++] = b; + } + } + + byte[] lenBytes = ByteBuffer.allocate(4).putInt(bytes.length).array(); + synchronized (mLogFile) { + mLogFile.write(MAGIC_BYTE); + mLogFile.write(lenBytes); + mLogFile.write(eBytes); + } } - private void openLogFile (File cacheDir) throws FileNotFoundException { + private void openLogFile(File cacheDir) throws FileNotFoundException { File logfile = new File(cacheDir, LOGFILE_NAME); mLogFile = new FileOutputStream(logfile); } private void readLogCache(File cacheDir) { - File logfile = new File(cacheDir, LOGFILE_NAME); - + try { + File logfile = new File(cacheDir, LOGFILE_NAME); - if (!logfile.exists() || !logfile.canRead()) - return; + if (!logfile.exists() || !logfile.canRead()) + return; + readCacheContents(new FileInputStream(logfile)); - try { + } catch (java.io.IOException | java.lang.RuntimeException e) { + VpnStatus.logError("Reading cached logfile failed"); + VpnStatus.logException(e); + e.printStackTrace(); + // ignore reading file error + } finally { + synchronized (VpnStatus.readFileLock) { + VpnStatus.readFileLog = true; + VpnStatus.readFileLock.notifyAll(); + } + } + } - BufferedInputStream logFile = new BufferedInputStream(new FileInputStream(logfile)); - byte[] buf = new byte[8192]; - int read = logFile.read(buf, 0, 4); - int itemsRead=0; + protected void readCacheContents(InputStream in) throws IOException { + BufferedInputStream logFile = new BufferedInputStream(in); - while (read >= 4) { - int len = ByteBuffer.wrap(buf, 0, 4).asIntBuffer().get(); + byte[] buf = new byte[16384]; + int read = logFile.read(buf, 0, 5); + int itemsRead = 0; - // Marshalled LogItem - read = logFile.read(buf, 0, len); - Parcel p = Parcel.obtain(); - p.unmarshall(buf, 0, read); - p.setDataPosition(0); - VpnStatus.LogItem li = VpnStatus.LogItem.CREATOR.createFromParcel(p); - if (li.verify()) { - VpnStatus.newLogItem(li, true); - } else { - VpnStatus.logError(String.format(Locale.getDefault(), - "Could not read log item from file: %d/%d: %s", - read, len, bytesToHex(buf, Math.max(read,80)))); + readloop: + while (read >= 5) { + int skipped = 0; + while (buf[skipped] != MAGIC_BYTE) { + skipped++; + if (!(logFile.read(buf, skipped + 4, 1) == 1) || skipped + 10 > buf.length) { + VpnStatus.logDebug(String.format(Locale.US, "Skipped %d bytes and no a magic byte found", skipped)); + break readloop; } - p.recycle(); - - //Next item - read = logFile.read(buf, 0, 4); - itemsRead++; - if (itemsRead > 2*VpnStatus.MAXLOGENTRIES) { - VpnStatus.logError("Too many logentries read from cache, aborting."); - read = 0; + } + if (skipped > 0) + VpnStatus.logDebug(String.format(Locale.US, "Skipped %d bytes before finding a magic byte", skipped)); + + int len = ByteBuffer.wrap(buf, skipped + 1, 4).asIntBuffer().get(); + + // Marshalled LogItem + int pos = 0; + byte buf2[] = new byte[buf.length]; + + while (pos < len) { + byte b = (byte) logFile.read(); + if (b == MAGIC_BYTE) { + VpnStatus.logDebug(String.format(Locale.US, "Unexpected magic byte found at pos %d, abort current log item", pos)); + read = logFile.read(buf, 1, 4) + 1; + continue readloop; + } else if (b == MAGIC_BYTE + 1) { + b = (byte) logFile.read(); + if (b == 0) + b = MAGIC_BYTE; + else if (b == 1) + b = MAGIC_BYTE + 1; + else { + VpnStatus.logDebug(String.format(Locale.US, "Escaped byte not 0 or 1: %d", b)); + read = logFile.read(buf, 1, 4) + 1; + continue readloop; + } } + buf2[pos++] = b; + } + + restoreLogItem(buf2, len); + //Next item + read = logFile.read(buf, 0, 5); + itemsRead++; + if (itemsRead > 2 * VpnStatus.MAXLOGENTRIES) { + VpnStatus.logError("Too many logentries read from cache, aborting."); + read = 0; } - VpnStatus.logDebug(R.string.reread_log, itemsRead); + } + VpnStatus.logDebug(R.string.reread_log, itemsRead); + } + protected void restoreLogItem(byte[] buf, int len) throws UnsupportedEncodingException { - } catch (java.io.IOException | java.lang.RuntimeException e) { - VpnStatus.logError("Reading cached logfile failed"); - VpnStatus.logException(e); - e.printStackTrace(); - // ignore reading file error + LogItem li = new LogItem(buf, len); + if (li.verify()) { + VpnStatus.newLogItem(li, true); + } else { + VpnStatus.logError(String.format(Locale.getDefault(), + "Could not read log item from file: %d: %s", + len, bytesToHex(buf, Math.max(len, 80)))); } } - final protected static char[] hexArray = "0123456789ABCDEF".toCharArray(); + private final static char[] hexArray = "0123456789ABCDEF".toCharArray(); + public static String bytesToHex(byte[] bytes, int len) { len = Math.min(bytes.length, len); char[] hexChars = new char[len * 2]; - for ( int j = 0; j < len; j++ ) { + for (int j = 0; j < len; j++) { int v = bytes[j] & 0xFF; hexChars[j * 2] = hexArray[v >>> 4]; hexChars[j * 2 + 1] = hexArray[v & 0x0F]; diff --git a/app/src/main/java/de/blinkt/openvpn/core/LogItem.java b/app/src/main/java/de/blinkt/openvpn/core/LogItem.java index 6aefbb2e..cd048f4a 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/LogItem.java +++ b/app/src/main/java/de/blinkt/openvpn/core/LogItem.java @@ -12,12 +12,10 @@ import android.content.pm.PackageManager; import android.content.pm.Signature; import android.os.Parcel; import android.os.Parcelable; -import android.text.TextUtils; -import android.util.Log; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.UnsupportedEncodingException; +import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -89,7 +87,7 @@ public class LogItem implements Parcelable { } - public byte[] getMarschaledBytes() throws UnsupportedEncodingException { + public byte[] getMarschaledBytes() throws UnsupportedEncodingException, BufferOverflowException { ByteBuffer bb = ByteBuffer.allocate(16384); 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 eb6d4d42..37689b3f 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java +++ b/app/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java @@ -34,8 +34,8 @@ public class NetworkSpace { /** * sorts the networks with following criteria: - * 1. compares first 1 of the network - * 2. smaller networks are returned as smaller + * 1. compares first 1 of the network + * 2. smaller networks are returned as smaller */ @Override public int compareTo(@NonNull ipAddress another) { @@ -55,8 +55,7 @@ public class NetworkSpace { /** * Warning ignores the included integer * - * @param o - * the object to compare this instance with. + * @param o the object to compare this instance with. */ @Override public boolean equals(Object o) { @@ -89,15 +88,15 @@ public class NetworkSpace { } public BigInteger getLastAddress() { - if(lastAddress ==null) + if (lastAddress == null) lastAddress = getMaskedAddress(true); return lastAddress; } public BigInteger getFirstAddress() { - if (firstAddress ==null) - firstAddress =getMaskedAddress(false); + if (firstAddress == null) + firstAddress = getMaskedAddress(false); return firstAddress; } @@ -126,7 +125,7 @@ public class NetworkSpace { public String toString() { //String in = included ? "+" : "-"; if (isV4) - return String.format(Locale.US,"%s/%d", getIPv4Address(), networkMask); + return String.format(Locale.US, "%s/%d", getIPv4Address(), networkMask); else return String.format(Locale.US, "%s/%d", getIPv6Address(), networkMask); } @@ -142,36 +141,48 @@ 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); - if (BuildConfig.DEBUG) Assert.assertTrue(secondHalf.getLastAddress().equals(getLastAddress())); + if (BuildConfig.DEBUG) + Assert.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); + Assert.assertTrue(isV4); + Assert.assertTrue(netAddress.longValue() <= 0xffffffffl); + Assert.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) Assert.assertTrue(!isV4); BigInteger r = netAddress; - Vector parts = new Vector(); - while (r.compareTo(BigInteger.ZERO) == 1 || parts.size() <3) { + String ipv6str = null; + boolean lastPart = true; + + while (r.compareTo(BigInteger.ZERO) == 1) { + long part = r.mod(BigInteger.valueOf(0x10000)).longValue(); - if (part!=0) - parts.add(0, String.format(Locale.US, "%x", part)); - else - parts.add(0, ""); + if (ipv6str != null || part != 0) { + if (ipv6str == null && !lastPart) + ipv6str = ":"; + + if (lastPart) + ipv6str = String.format(Locale.US, "%x", part, ipv6str); + else + ipv6str = String.format(Locale.US, "%x:%s", part, ipv6str); + } + r = r.shiftRight(16); + lastPart = false; } - String ipv6str = TextUtils.join(":", parts); - while (ipv6str.contains(":::")) - ipv6str = ipv6str.replace(":::", "::"); + if (ipv6str == null) + return "::"; + + return ipv6str; } @@ -183,8 +194,8 @@ public class NetworkSpace { BigInteger netLast = network.getLastAddress(); boolean a = ourFirst.compareTo(netFirst) != 1; - boolean b = ourLast.compareTo(netLast) != -1; - return a && b; + boolean b = ourLast.compareTo(netLast) != -1; + return a && b; } } @@ -215,7 +226,7 @@ public class NetworkSpace { public void addIPSplit(CIDRIP cidrIp, boolean include) { ipAddress newIP = new ipAddress(cidrIp, include); ipAddress[] splitIps = newIP.split(); - for (ipAddress split: splitIps) + for (ipAddress split : splitIps) mIpAddresses.add(split); } @@ -229,16 +240,16 @@ public class NetworkSpace { TreeSet ipsDone = new TreeSet(); - ipAddress currentNet = networks.poll(); - if (currentNet==null) + ipAddress currentNet = networks.poll(); + if (currentNet == null) return ipsDone; - while (currentNet!=null) { + while (currentNet != null) { // Check if it and the next of it are compatible ipAddress nextNet = networks.poll(); if (BuildConfig.DEBUG) Assert.assertNotNull(currentNet); - if (nextNet== null || currentNet.getLastAddress().compareTo(nextNet.getFirstAddress()) == -1) { + if (nextNet == null || currentNet.getLastAddress().compareTo(nextNet.getFirstAddress()) == -1) { // Everything good, no overlapping nothing to do ipsDone.add(currentNet); @@ -249,7 +260,7 @@ public class NetworkSpace { if (currentNet.included == nextNet.included) { // Included in the next next and same type // Simply forget our current network - currentNet=nextNet; + currentNet = nextNet; } else { // our currentNet is included in next and types differ. Need to split the next network ipAddress[] newNets = nextNet.split(); @@ -262,7 +273,8 @@ public class NetworkSpace { networks.add(newNets[1]); if (newNets[0].getLastAddress().equals(currentNet.getLastAddress())) { - if (BuildConfig.DEBUG) Assert.assertEquals (newNets[0].networkMask, currentNet.networkMask); + if (BuildConfig.DEBUG) + Assert.assertEquals(newNets[0].networkMask, currentNet.networkMask); // Don't add the lower half that would conflict with currentNet } else { if (!networks.contains(newNets[0])) @@ -273,8 +285,8 @@ 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); + Assert.assertTrue(nextNet.getFirstAddress().compareTo(currentNet.getFirstAddress()) == 1); + Assert.assertTrue(currentNet.getLastAddress().compareTo(nextNet.getLastAddress()) != -1); } // This network is bigger than the next and last ip of current >= next @@ -289,8 +301,8 @@ public class NetworkSpace { 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())); + Assert.assertTrue(newNets[1].getFirstAddress().equals(nextNet.getFirstAddress())); + Assert.assertTrue(newNets[1].getLastAddress().equals(currentNet.getLastAddress())); // split second equal the next network, do not add it } networks.add(nextNet); @@ -322,20 +334,20 @@ 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; // The netspace exists - if(ipsSorted.contains(origIp)) + if (ipsSorted.contains(origIp)) continue; - boolean skipIp=false; + boolean skipIp = false; // If there is any smaller net that is excluded we may not add the positive route back - for (ipAddress calculatedIp: ipsSorted) { - if(!calculatedIp.included && origIp.containsNet(calculatedIp)) { - skipIp=true; + for (ipAddress calculatedIp : ipsSorted) { + if (!calculatedIp.included && origIp.containsNet(calculatedIp)) { + skipIp = true; break; } } diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java index 2911fb1e..ef17e98b 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java @@ -13,7 +13,7 @@ public interface OpenVPNManagement { enum pauseReason { noNetwork, userPause, - screenOff + screenOff, } int mBytecountInterval = 2; 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 2917bce1..5faa1de4 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java @@ -16,17 +16,21 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageManager; +import android.content.pm.ShortcutManager; import android.content.res.Configuration; +import android.content.res.Resources; import android.net.ConnectivityManager; import android.net.VpnService; -import android.os.Binder; 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.preference.PreferenceManager; +import android.os.RemoteException; +import android.support.annotation.NonNull; +import android.support.annotation.RequiresApi; import android.system.OsConstants; import android.text.TextUtils; import android.util.Log; @@ -39,38 +43,38 @@ import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Collection; -import java.util.HashMap; import java.util.Locale; import java.util.Vector; import se.leap.bitmaskclient.BuildConfig; +import de.blinkt.openvpn.LaunchVPN; import se.leap.bitmaskclient.R; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.activities.DisconnectVPN; import de.blinkt.openvpn.core.VpnStatus.ByteCountListener; -import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus; import de.blinkt.openvpn.core.VpnStatus.StateListener; +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.VpnStatus.ConnectionStatus.LEVEL_CONNECTED; -import static de.blinkt.openvpn.core.VpnStatus.ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET; -import static de.blinkt.openvpn.core.VpnStatus.ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT; import se.leap.bitmaskclient.Dashboard; -public class OpenVPNService extends VpnService implements StateListener, Callback, ByteCountListener { +public class OpenVPNService extends VpnService implements StateListener, Callback, ByteCountListener, IOpenVPNServiceInternal { public static final String START_SERVICE = "de.blinkt.openvpn.START_SERVICE"; 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"; - private static final int OPENVPN_STATUS = 1; + public static final String NOTIFICATION_CHANNEL_BG_ID = "openvpn_bg"; + public static final String NOTIFICATION_CHANNEL_NEWSTATUS_ID = "openvpn_newstat"; + private String lastChannel; + private static boolean mNotificationAlwaysVisible = false; private final Vector mDnslist = new Vector<>(); private final NetworkSpace mRoutes = new NetworkSpace(); private final NetworkSpace mRoutesv6 = new NetworkSpace(); - private final IBinder mBinder = new LocalBinder(); private Thread mProcessThread = null; private VpnProfile mProfile; private String mDomain = null; @@ -90,20 +94,65 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac private Toast mlastToast; private Runnable mOpenVPNThread; + private static final int PRIORITY_MIN = -2; + private static final int PRIORITY_DEFAULT = 0; + private static final int PRIORITY_MAX = 2; + + + private final IBinder mBinder = new IOpenVPNServiceInternal.Stub() { + + @Override + public boolean protect(int fd) throws RemoteException { + return OpenVPNService.this.protect(fd); + } + + @Override + public void userPause(boolean shouldbePaused) throws RemoteException { + OpenVPNService.this.userPause(shouldbePaused); + } + + @Override + public boolean stopVPN(boolean replaceConnection) throws RemoteException { + return OpenVPNService.this.stopVPN(replaceConnection); + } + }; + // From: http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java - public static String humanReadableByteCount(long bytes, boolean mbit) { - if (mbit) + public static String humanReadableByteCount(long bytes, boolean speed, Resources res) { + if (speed) bytes = bytes * 8; - int unit = mbit ? 1000 : 1024; - if (bytes < unit) - return bytes + (mbit ? " bit" : " B"); - - int exp = (int) (Math.log(bytes) / Math.log(unit)); - String pre = (mbit ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (mbit ? "" : ""); - if (mbit) - return String.format(Locale.getDefault(), "%.1f %sbit", bytes / Math.pow(unit, exp), pre); + int unit = speed ? 1000 : 1024; + + + int exp = Math.max(0, Math.min((int) (Math.log(bytes) / Math.log(unit)), 3)); + + float bytesUnit = (float) (bytes / Math.pow(unit, exp)); + + if (speed) + switch (exp) { + case 0: + return res.getString(R.string.bits_per_second, bytesUnit); + case 1: + return res.getString(R.string.kbits_per_second, bytesUnit); + case 2: + return res.getString(R.string.mbits_per_second, bytesUnit); + default: + return res.getString(R.string.gbits_per_second, bytesUnit); + } else - return String.format(Locale.getDefault(), "%.1f %sB", bytes / Math.pow(unit, exp), pre); + switch (exp) { + case 0: + return res.getString(R.string.volume_byte, bytesUnit); + case 1: + return res.getString(R.string.volume_kbyte, bytesUnit); + case 2: + return res.getString(R.string.volume_mbyte, bytesUnit); + default: + return res.getString(R.string.volume_gbyte, bytesUnit); + + } + + } @Override @@ -117,7 +166,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac @Override public void onRevoke() { - VpnStatus.logInfo(R.string.permission_revoked); + VpnStatus.logError(R.string.permission_revoked); mManagement.stopVPN(false); endVpnService(); } @@ -145,26 +194,33 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } } - private void showNotification(final String msg, String tickerText, boolean lowpriority, long when, ConnectionStatus status) { - String ns = Context.NOTIFICATION_SERVICE; - NotificationManager mNotificationManager = (NotificationManager) getSystemService(ns); - + private void showNotification(final String msg, String tickerText, @NonNull String channel, long when, ConnectionStatus status) { + NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); int icon = getIconByConnectionStatus(status); android.app.Notification.Builder nbuilder = new Notification.Builder(this); + int priority; + if (channel.equals(NOTIFICATION_CHANNEL_BG_ID)) + priority = PRIORITY_MIN; + else + priority = PRIORITY_DEFAULT; + if (mProfile != null) - nbuilder.setContentTitle(getString(R.string.notifcation_title, mProfile.mName)); + nbuilder.setContentTitle(getString(R.string.notifcation_title_bitmask, mProfile.mName)); else nbuilder.setContentTitle(getString(R.string.notifcation_title_notconnect)); nbuilder.setContentText(msg); nbuilder.setOnlyAlertOnce(true); nbuilder.setOngoing(true); - nbuilder.setContentIntent(getLogPendingIntent()); - nbuilder.setSmallIcon(icon); + nbuilder.setSmallIcon(icon); + if (status == LEVEL_WAITING_FOR_USER_INPUT) + nbuilder.setContentIntent(getUserInputIntent(msg)); + else + nbuilder.setContentIntent(getGraphPendingIntent()); if (when != 0) nbuilder.setWhen(when); @@ -172,23 +228,39 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac // Try to set the priority available since API 16 (Jellybean) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) - jbNotificationExtras(lowpriority, nbuilder); + + jbNotificationExtras(priority, nbuilder); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) lpNotificationExtras(nbuilder); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + //noinspection NewApi + nbuilder.setChannelId(channel); + if (mProfile != null) + //noinspection NewApi + nbuilder.setShortcutId(mProfile.getUUIDString()); + + } + if (tickerText != null && !tickerText.equals("")) nbuilder.setTicker(tickerText); @SuppressWarnings("deprecation") Notification notification = nbuilder.getNotification(); + int notificationId = channel.hashCode(); - mNotificationManager.notify(OPENVPN_STATUS, notification); - // startForeground(OPENVPN_STATUS, notification); + mNotificationManager.notify(notificationId, notification); + startForeground(notificationId, notification); + + if (lastChannel != null && !channel.equals(lastChannel)) { + // Cancel old notification + mNotificationManager.cancel(lastChannel.hashCode()); + } // Check if running on a TV - if (runningOnAndroidTV() && !lowpriority) + if (runningOnAndroidTV() && !(priority < 0)) guiHandler.post(new Runnable() { @Override @@ -238,13 +310,12 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } @TargetApi(Build.VERSION_CODES.JELLY_BEAN) - private void jbNotificationExtras(boolean lowpriority, + private void jbNotificationExtras(int priority, android.app.Notification.Builder nbuilder) { try { - if (lowpriority) { + if (priority != 0) { Method setpriority = nbuilder.getClass().getMethod("setPriority", int.class); - // PRIORITY_MIN == -2 - setpriority.invoke(nbuilder, -2); + setpriority.invoke(nbuilder, priority); Method setUsesChronometer = nbuilder.getClass().getMethod("setUsesChronometer", boolean.class); setUsesChronometer.invoke(nbuilder, true); @@ -281,9 +352,20 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } - PendingIntent getLogPendingIntent() { + PendingIntent getUserInputIntent(String needed) { + Intent intent = new Intent(getApplicationContext(), LaunchVPN.class); + intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); + intent.putExtra("need", needed); + Bundle b = new Bundle(); + b.putString("need", needed); + PendingIntent pIntent = PendingIntent.getActivity(this, 12, intent, 0); + return pIntent; + } + + PendingIntent getGraphPendingIntent() { // Let the configure Button show the Log Intent intent = new Intent(getBaseContext(), Dashboard.class); + intent.putExtra("PAGE", "graph"); intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); PendingIntent startLW = PendingIntent.getActivity(this, 0, intent, 0); intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); @@ -298,6 +380,10 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(Intent.ACTION_SCREEN_ON); mDeviceStateReceiver = new DeviceStateReceiver(magnagement); + + // Fetch initial network state + mDeviceStateReceiver.networkStateChange(this); + registerReceiver(mDeviceStateReceiver, filter); VpnStatus.addByteCountListener(mDeviceStateReceiver); @@ -328,6 +414,14 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac mDeviceStateReceiver.userPause(shouldBePaused); } + @Override + public boolean stopVPN(boolean replaceConnection) throws RemoteException { + if (getManagement() != null) + return getManagement().stopVPN(replaceConnection); + else + return false; + } + @Override public int onStartCommand(Intent intent, int flags, int startId) { @@ -359,26 +453,32 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac return START_REDELIVER_INTENT; } - /* The intent is null when the service has been restarted */ - if (intent == null) { - mProfile = ProfileManager.getLastConnectedProfile(this, false); + if (intent != null && intent.hasExtra(getPackageName() + ".profileUUID")) { + String profileUUID = intent.getStringExtra(getPackageName() + ".profileUUID"); + int profileVersion = intent.getIntExtra(getPackageName() + ".profileVersion", 0); + // Try for 10s to get current version of the profile + mProfile = ProfileManager.get(this, profileUUID, profileVersion, 100); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { + updateShortCutUsage(mProfile); + } + + } else { + /* The intent is null when we are set as always-on or the service has been restarted. */ + mProfile = ProfileManager.getLastConnectedProfile(this); VpnStatus.logInfo(R.string.service_restarted); /* Got no profile, just stop */ if (mProfile == null) { - Log.d("OpenVPN", "Got no last connected profile on null intent. Stopping"); - stopSelf(startId); - return START_NOT_STICKY; + Log.d("OpenVPN", "Got no last connected profile on null intent. Assuming always on."); + mProfile = ProfileManager.getAlwaysOnVPN(this); + + if (mProfile == null) { + stopSelf(startId); + return START_NOT_STICKY; + } } /* Do the asynchronous keychain certificate stuff */ mProfile.checkForRestart(this); - - /* Recreate the intent */ - intent = mProfile.getStartServiceIntent(this); - - } else { - String profileUUID = intent.getStringExtra(getPackageName() + ".profileUUID"); - mProfile = ProfileManager.get(this, profileUUID); } /* start the OpenVPN process itself in a background thread */ @@ -391,17 +491,22 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac ProfileManager.setConnectedVpnProfile(this, mProfile); - /* TODO: At the moment we have no way to handle asynchronous PW input - * Fixing will also allow to handle challenge/response authentication */ - if (mProfile.needUserPWInput(true) != 0) - return START_NOT_STICKY; + VpnStatus.setConnectedVPNProfile(mProfile.getUUIDString()); return START_STICKY; } + @RequiresApi(Build.VERSION_CODES.N_MR1) + private void updateShortCutUsage(VpnProfile profile) { + if (profile == null) + return; + ShortcutManager shortcutManager = getSystemService(ShortcutManager.class); + shortcutManager.reportShortcutUsed(profile.getUUIDString()); + } + private void startOpenVPN() { VpnStatus.logInfo(R.string.building_configration); - VpnStatus.updateStateString("VPN_GENERATE_CONFIG", "", R.string.building_configration, VpnStatus.ConnectionStatus.LEVEL_START); + VpnStatus.updateStateString("VPN_GENERATE_CONFIG", "", R.string.building_configration, ConnectionStatus.LEVEL_START); try { @@ -411,12 +516,9 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac endVpnService(); return; } - - // Extract information from the intent. - String prefix = getPackageName(); String nativeLibraryDirectory = getApplicationInfo().nativeLibraryDir; - // Also writes OpenVPN binary + // Write OpenVPN binary String[] argv = VPNLaunchHelper.buildOpenvpnArgv(this); @@ -429,7 +531,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac mStarting = false; // Start a new session by creating a new thread. - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + SharedPreferences prefs = Preferences.getDefaultSharedPreferences(this); mOvpn3 = prefs.getBoolean("ovpn3", false); if (!"ovpn3".equals(BuildConfig.FLAVOR)) @@ -462,8 +564,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } else { - HashMap env = new HashMap<>(); - processThread = new OpenVPNThread(this, argv, env, nativeLibraryDirectory); + processThread = new OpenVPNThread(this, argv, nativeLibraryDirectory); mOpenVPNThread = processThread; } @@ -475,21 +576,21 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } new Handler(getMainLooper()).post(new Runnable() { - @Override - public void run() { - if (mDeviceStateReceiver != null) - unregisterDeviceStateReceiver(); + @Override + public void run() { + if (mDeviceStateReceiver != null) + unregisterDeviceStateReceiver(); - registerDeviceStateReceiver(mManagement); - } - } + registerDeviceStateReceiver(mManagement); + } + } - ); + ); } private void stopOldOpenVPNProcess() { if (mManagement != null) { - if (mOpenVPNThread!=null) + if (mOpenVPNThread != null) ((OpenVPNThread) mOpenVPNThread).setReplaceConnection(); if (mManagement.stopVPN(true)) { // an old was asked to exit, wait 1s @@ -501,6 +602,10 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } } + forceStopOpenVpnProcess(); + } + + public void forceStopOpenVpnProcess() { synchronized (mProcessLock) { if (mProcessThread != null) { mProcessThread.interrupt(); @@ -524,6 +629,16 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac return null; } + @Override + public IBinder asBinder() { + return mBinder; + } + + @Override + public void onCreate() { + super.onCreate(); + } + @Override public void onDestroy() { synchronized (mProcessLock) { @@ -538,7 +653,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac // Just in case unregister for state VpnStatus.removeStateListener(this); VpnStatus.flushLog(); - } private String getTunConfigString() { @@ -636,7 +750,9 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac positiveIPv4Routes.add(dnsServer); } } catch (Exception e) { - VpnStatus.logError("Error parsing DNS Server IP: " + mDnslist.get(0)); + // If it looks like IPv6 ignore error + if (!mDnslist.get(0).contains(":")) + VpnStatus.logError("Error parsing DNS Server IP: " + mDnslist.get(0)); } } @@ -698,7 +814,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac mLocalIPv6 = null; mDomain = null; - builder.setConfigureIntent(getLogPendingIntent()); + builder.setConfigureIntent(getGraphPendingIntent()); try { //Debug.stopMethodTracing(); @@ -812,7 +928,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac NetworkSpace.ipAddress gatewayIP = new NetworkSpace.ipAddress(new CIDRIP(gateway, 32), false); if (mLocalIP == null) { - VpnStatus.logError("Local IP address unset but adding route?! This is broken! Please contact author with log"); + 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); @@ -924,7 +1040,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac if (mProcessThread == null && !mNotificationAlwaysVisible) return; - boolean lowpriority = false; + String channel = NOTIFICATION_CHANNEL_NEWSTATUS_ID; // Display byte count only after being connected { @@ -936,11 +1052,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac mDisplayBytecount = true; mConnecttime = System.currentTimeMillis(); if (!runningOnAndroidTV()) - lowpriority = true; - - String ns = Context.NOTIFICATION_SERVICE; - NotificationManager mNotificationManager = (NotificationManager) getSystemService(ns); - mNotificationManager.cancel(OPENVPN_STATUS); + channel = NOTIFICATION_CHANNEL_BG_ID; } else { mDisplayBytecount = false; } @@ -949,13 +1061,16 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac // This also mean we are no longer connected, ignore bytecount messages until next // CONNECTED // Does not work :( - String msg = getString(resid); - // showNotification(VpnStatus.getLastCleanLogMessage(this), - // msg, lowpriority, 0, level); + showNotification(VpnStatus.getLastCleanLogMessage(this), + VpnStatus.getLastCleanLogMessage(this), channel, 0, level); } } + @Override + public void setConnectedVPN(String uuid) { + } + private void doSendBroadcast(String state, ConnectionStatus level) { Intent vpnstatus = new Intent(); vpnstatus.setAction("de.blinkt.openvpn.VPN_STATUS"); @@ -968,13 +1083,13 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac public void updateByteCount(long in, long out, long diffIn, long diffOut) { if (mDisplayBytecount) { String netstat = String.format(getString(R.string.statusline_bytecount), - humanReadableByteCount(in, false), - humanReadableByteCount(diffIn / OpenVPNManagement.mBytecountInterval, true), - humanReadableByteCount(out, false), - humanReadableByteCount(diffOut / OpenVPNManagement.mBytecountInterval, true)); + humanReadableByteCount(in, false, getResources()), + humanReadableByteCount(diffIn / OpenVPNManagement.mBytecountInterval, true, getResources()), + humanReadableByteCount(out, false, getResources()), + humanReadableByteCount(diffOut / OpenVPNManagement.mBytecountInterval, true, getResources())); - boolean lowpriority = !mNotificationAlwaysVisible; - //showNotification(netstat, null, lowpriority, mConnecttime, LEVEL_CONNECTED); + + showNotification(netstat, null, NOTIFICATION_CHANNEL_BG_ID, mConnecttime, LEVEL_CONNECTED); } } @@ -1009,10 +1124,8 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } } - public class LocalBinder extends Binder { - public OpenVPNService getService() { - // Return this instance of LocalService so clients can call public methods - return OpenVPNService.this; - } + public void requestInputFromUser(int resid, String needed) { + VpnStatus.updateStateString("NEED", "need " + needed, resid, LEVEL_WAITING_FOR_USER_INPUT); + showNotification(getString(resid), getString(resid), NOTIFICATION_CHANNEL_NEWSTATUS_ID, 0, LEVEL_WAITING_FOR_USER_INPUT); } } diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNStatusService.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNStatusService.java new file mode 100644 index 00000000..6df1379a --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNStatusService.java @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2012-2016 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.app.Service; +import android.content.Intent; +import android.os.Build; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.ParcelFileDescriptor; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.support.annotation.Nullable; +import android.util.Pair; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.ref.WeakReference; + +/** + * Created by arne on 08.11.16. + */ + +public class OpenVPNStatusService extends Service implements VpnStatus.LogListener, VpnStatus.ByteCountListener, VpnStatus.StateListener { + @Nullable + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + + static final RemoteCallbackList mCallbacks = + new RemoteCallbackList<>(); + + @Override + public void onCreate() { + super.onCreate(); + VpnStatus.addLogListener(this); + VpnStatus.addByteCountListener(this); + VpnStatus.addStateListener(this); + mHandler.setService(this); + + } + + @Override + public void onDestroy() { + super.onDestroy(); + + VpnStatus.removeLogListener(this); + VpnStatus.removeByteCountListener(this); + VpnStatus.removeStateListener(this); + mCallbacks.kill(); + + } + + private static final IServiceStatus.Stub mBinder = new IServiceStatus.Stub() { + + @Override + public ParcelFileDescriptor registerStatusCallback(IStatusCallbacks cb) throws RemoteException { + final LogItem[] logbuffer = VpnStatus.getlogbuffer(); + if (mLastUpdateMessage != null) + sendUpdate(cb, mLastUpdateMessage); + + mCallbacks.register(cb); + try { + final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); + new Thread("pushLogs") { + @Override + public void run() { + DataOutputStream fd = new DataOutputStream(new ParcelFileDescriptor.AutoCloseOutputStream(pipe[1])); + try { + synchronized (VpnStatus.readFileLock) { + if (!VpnStatus.readFileLog) { + VpnStatus.readFileLock.wait(); + } + } + } catch (InterruptedException e) { + VpnStatus.logException(e); + } + try { + + for (LogItem logItem : logbuffer) { + byte[] bytes = logItem.getMarschaledBytes(); + fd.writeShort(bytes.length); + fd.write(bytes); + } + // Mark end + fd.writeShort(0x7fff); + fd.close(); + } catch (IOException e) { + e.printStackTrace(); + } + + } + }.start(); + return pipe[0]; + } catch (IOException e) { + e.printStackTrace(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { + throw new RemoteException(e.getMessage()); + } + return null; + } + } + + @Override + public void unregisterStatusCallback(IStatusCallbacks cb) throws RemoteException { + mCallbacks.unregister(cb); + } + + @Override + public String getLastConnectedVPN() throws RemoteException { + return VpnStatus.getLastConnectedVPNProfile(); + } + + @Override + public void setCachedPassword(String uuid, int type, String password) { + PasswordCache.setCachedPassword(uuid, type, password); + } + + @Override + public TrafficHistory getTrafficHistory() throws RemoteException { + return VpnStatus.trafficHistory; + } + + }; + + @Override + public void newLog(LogItem logItem) { + Message msg = mHandler.obtainMessage(SEND_NEW_LOGITEM, logItem); + msg.sendToTarget(); + } + + @Override + public void updateByteCount(long in, long out, long diffIn, long diffOut) { + Message msg = mHandler.obtainMessage(SEND_NEW_BYTECOUNT, Pair.create(in, out)); + msg.sendToTarget(); + } + + static UpdateMessage mLastUpdateMessage; + + static class UpdateMessage { + public String state; + public String logmessage; + public ConnectionStatus level; + int resId; + + UpdateMessage(String state, String logmessage, int resId, ConnectionStatus level) { + this.state = state; + this.resId = resId; + this.logmessage = logmessage; + this.level = level; + } + } + + + @Override + public void updateState(String state, String logmessage, int localizedResId, ConnectionStatus level) { + + mLastUpdateMessage = new UpdateMessage(state, logmessage, localizedResId, level); + Message msg = mHandler.obtainMessage(SEND_NEW_STATE, mLastUpdateMessage); + msg.sendToTarget(); + } + + @Override + public void setConnectedVPN(String uuid) { + Message msg = mHandler.obtainMessage(SEND_NEW_CONNECTED_VPN, uuid); + msg.sendToTarget(); + } + + private static final OpenVPNStatusHandler mHandler = new OpenVPNStatusHandler(); + + private static final int SEND_NEW_LOGITEM = 100; + private static final int SEND_NEW_STATE = 101; + private static final int SEND_NEW_BYTECOUNT = 102; + private static final int SEND_NEW_CONNECTED_VPN = 103; + + private static class OpenVPNStatusHandler extends Handler { + WeakReference service = null; + + private void setService(OpenVPNStatusService statusService) { + service = new WeakReference<>(statusService); + } + + @Override + public void handleMessage(Message msg) { + + RemoteCallbackList callbacks; + if (service == null || service.get() == null) + return; + callbacks = service.get().mCallbacks; + // Broadcast to all clients the new value. + final int N = callbacks.beginBroadcast(); + for (int i = 0; i < N; i++) { + + try { + IStatusCallbacks broadcastItem = callbacks.getBroadcastItem(i); + + switch (msg.what) { + case SEND_NEW_LOGITEM: + broadcastItem.newLogItem((LogItem) msg.obj); + break; + case SEND_NEW_BYTECOUNT: + Pair inout = (Pair) msg.obj; + broadcastItem.updateByteCount(inout.first, inout.second); + break; + case SEND_NEW_STATE: + sendUpdate(broadcastItem, (UpdateMessage) msg.obj); + break; + + case SEND_NEW_CONNECTED_VPN: + broadcastItem.connectedVPN((String) msg.obj); + break; + } + } catch (RemoteException e) { + // The RemoteCallbackList will take care of removing + // the dead object for us. + } + } + callbacks.finishBroadcast(); + } + } + + private static void sendUpdate(IStatusCallbacks broadcastItem, + UpdateMessage um) throws RemoteException { + broadcastItem.updateStateString(um.state, um.logmessage, um.resId, um.level); + } +} \ No newline at end of file diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java index e0c39546..c96f88c4 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java @@ -19,15 +19,10 @@ import java.util.Collections; import java.util.Date; import java.util.LinkedList; import java.util.Locale; -import java.util.Map; -import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; import se.leap.bitmaskclient.R; -import de.blinkt.openvpn.VpnProfile; -import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus; -import de.blinkt.openvpn.core.VpnStatus.LogItem; public class OpenVPNThread implements Runnable { private static final String DUMP_PATH_STRING = "Dump path: "; @@ -44,15 +39,13 @@ public class OpenVPNThread implements Runnable { private String mNativeDir; private OpenVPNService mService; private String mDumpPath; - private Map mProcessEnv; private boolean mBrokenPie = false; private boolean mNoProcessExitStatus = false; - public OpenVPNThread(OpenVPNService service, String[] argv, Map processEnv, String nativelibdir) { + public OpenVPNThread(OpenVPNService service, String[] argv, String nativelibdir) { mArgv = argv; mNativeDir = nativelibdir; mService = service; - mProcessEnv = processEnv; } public void stopProcess() { @@ -68,7 +61,7 @@ public class OpenVPNThread implements Runnable { public void run() { try { Log.i(TAG, "Starting openvpn"); - startOpenVPNThreadArgs(mArgv, mProcessEnv); + startOpenVPNThreadArgs(mArgv); Log.i(TAG, "OpenVPN process exited"); } catch (Exception e) { VpnStatus.logException("Starting OpenVPN Thread", e); @@ -94,7 +87,6 @@ public class OpenVPNThread implements Runnable { mArgv = noPieArgv; VpnStatus.logInfo("PIE Version could not be executed. Trying no PIE version"); run(); - return; } } @@ -124,7 +116,7 @@ public class OpenVPNThread implements Runnable { } } - private void startOpenVPNThreadArgs(String[] argv, Map env) { + private void startOpenVPNThreadArgs(String[] argv) { LinkedList argvlist = new LinkedList(); Collections.addAll(argvlist, argv); @@ -136,10 +128,6 @@ public class OpenVPNThread implements Runnable { pb.environment().put("LD_LIBRARY_PATH", lbpath); - // Add extra variables - for (Entry e : env.entrySet()) { - pb.environment().put(e.getKey(), e.getValue()); - } pb.redirectErrorStream(true); try { mProcess = pb.start(); @@ -164,6 +152,7 @@ public class OpenVPNThread implements Runnable { Pattern p = Pattern.compile("(\\d+).(\\d+) ([0-9a-f])+ (.*)"); Matcher m = p.matcher(logline); + int logerror = 0; if (m.matches()) { int flags = Integer.parseInt(m.group(3), 16); String msg = m.group(4); @@ -183,15 +172,22 @@ public class OpenVPNThread implements Runnable { if (msg.startsWith("MANAGEMENT: CMD")) logLevel = Math.max(4, logLevel); + if ((msg.endsWith("md too weak") && msg.startsWith("OpenSSL: error")) || msg.contains("error:140AB18E")) + logerror = 1; VpnStatus.logMessageOpenVPN(logStatus, logLevel, msg); + if (logerror==1) + VpnStatus.logError("OpenSSL reproted a certificate with a weak hash, please the in app FAQ about weak hashes"); + } else { VpnStatus.logInfo("P:" + logline); } - } - - } catch (IOException e) { + if (Thread.interrupted()) { + throw new InterruptedException("OpenVpn process was killed form java code"); + } + } + } catch (InterruptedException | IOException e) { VpnStatus.logException("Error reading from output of OpenVPN process", e); stopProcess(); } 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 569a3846..492e8913 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java @@ -9,8 +9,10 @@ import android.content.Context; import android.net.LocalServerSocket; import android.net.LocalSocket; import android.net.LocalSocketAddress; +import android.os.Handler; import android.os.ParcelFileDescriptor; import android.support.annotation.NonNull; +import android.text.TextUtils; import android.util.Log; import junit.framework.Assert; @@ -31,11 +33,11 @@ import java.util.Vector; import se.leap.bitmaskclient.BuildConfig; import se.leap.bitmaskclient.R; import de.blinkt.openvpn.VpnProfile; -import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus; public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { private static final String TAG = "openvpn"; + private final Handler mResumeHandler; private LocalSocket mSocket; private VpnProfile mProfile; private OpenVPNService mOpenVPNService; @@ -54,8 +56,19 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { public OpenVpnManagementThread(VpnProfile profile, OpenVPNService openVpnService) { mProfile = profile; mOpenVPNService = openVpnService; + mResumeHandler = new Handler(openVpnService.getMainLooper()); + } + private Runnable mResumeHoldRunnable = new Runnable() { + @Override + public void run() { + if (shouldBeRunning()) { + releaseHoldCmd(); + } + } + }; + public boolean openManagementInterface(@NonNull Context c) { // Could take a while to open connection int tries = 8; @@ -92,15 +105,21 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { } - public void managmentCommand(String cmd) { + /** + * @param cmd command to write to management socket + * @return true if command have been sent + */ + public boolean managmentCommand(String cmd) { try { if (mSocket != null && mSocket.getOutputStream() != null) { mSocket.getOutputStream().write(cmd.getBytes()); mSocket.getOutputStream().flush(); + return true; } } catch (IOException e) { // Ignore socket stack traces } + return false; } @@ -118,13 +137,20 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { // Wait for a client to connect mSocket = mServerSocket.accept(); InputStream instream = mSocket.getInputStream(); + + // Close the management socket after client connected + try { + mServerSocket.close(); + } catch (IOException e) { + VpnStatus.logException(e); + } - mServerSocket.close(); // Closing one of the two sockets also closes the other //mServerSocketLocal.close(); while (true) { + int numbytesread = instream.read(buffer); if (numbytesread == -1) return; @@ -215,7 +241,7 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { processPWCommand(argument); break; case "HOLD": - handleHold(); + handleHold(argument); break; case "NEED-OK": processNeedCommand(argument); @@ -305,19 +331,26 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { return mPauseCallback.shouldBeRunning(); } - private void handleHold() { + private void handleHold(String argument) { + mWaitingForRelease = true; + int waittime = Integer.parseInt(argument.split(":")[1]); if (shouldBeRunning()) { - releaseHoldCmd(); - } else { - mWaitingForRelease = true; + if (waittime > 1) + VpnStatus.updateStateString("CONNECTRETRY", String.valueOf(waittime), + R.string.state_waitconnectretry, ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET); + mResumeHandler.postDelayed(mResumeHoldRunnable, waittime * 1000); + if (waittime > 5) + VpnStatus.logInfo(R.string.state_waitconnectretry, String.valueOf(waittime)); + else + VpnStatus.logDebug(R.string.state_waitconnectretry, String.valueOf(waittime)); + } else { VpnStatus.updateStatePause(lastPauseReason); - - } } private void releaseHoldCmd() { + mResumeHandler.removeCallbacks(mResumeHoldRunnable); if ((System.currentTimeMillis() - mLastHoldRelease) < 5000) { try { Thread.sleep(3000); @@ -402,6 +435,7 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { protectFileDescriptor(fdtoprotect); break; case "DNSSERVER": + case "DNS6SERVER": mOpenVPNService.addDNS(extra); break; case "DNSDOMAIN": @@ -529,15 +563,17 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { if (needed.equals("Private Key")) { pw = mProfile.getPasswordPrivateKey(); } else if (needed.equals("Auth")) { + pw = mProfile.getPasswordAuth(); + String usercmd = String.format("username '%s' %s\n", needed, VpnProfile.openVpnEscape(mProfile.mUsername)); managmentCommand(usercmd); - pw = mProfile.getPasswordAuth(); } if (pw != null) { String cmd = String.format("password '%s' %s\n", needed, VpnProfile.openVpnEscape(pw)); managmentCommand(cmd); } else { + mOpenVPNService.requestInputFromUser(R.string.password, needed); VpnStatus.logError(String.format("Openvpn requires Authentication type '%s' but no password/key information available", needed)); } @@ -553,8 +589,7 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { synchronized (active) { boolean sendCMD = false; for (OpenVpnManagementThread mt : active) { - mt.managmentCommand("signal SIGINT\n"); - sendCMD = true; + sendCMD = mt.managmentCommand("signal SIGINT\n"); try { if (mt.mSocket != null) mt.mSocket.close(); @@ -571,7 +606,7 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { if (mWaitingForRelease) releaseHold(); else if (samenetwork) - managmentCommand("network-change samenetwork\n"); + managmentCommand("network-change\n"); else managmentCommand("network-change\n"); } @@ -582,6 +617,7 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { } public void signalusr1() { + mResumeHandler.removeCallbacks(mResumeHoldRunnable); if (!mWaitingForRelease) managmentCommand("signal SIGUSR1\n"); else @@ -624,8 +660,12 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { @Override public boolean stopVPN(boolean replaceConnection) { - mShuttingDown = true; - return stopOpenVPN(); + boolean stopSucceed = stopOpenVPN(); + if (stopSucceed) { + mShuttingDown = true; + + } + return stopSucceed; } } diff --git a/app/src/main/java/de/blinkt/openvpn/core/PasswordCache.java b/app/src/main/java/de/blinkt/openvpn/core/PasswordCache.java new file mode 100644 index 00000000..179a8a7b --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/core/PasswordCache.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2012-2016 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 java.util.UUID; + +/** + * Created by arne on 15.12.16. + */ + +public class PasswordCache { + public static final int PCKS12ORCERTPASSWORD = 2; + public static final int AUTHPASSWORD = 3; + private static PasswordCache mInstance; + final private UUID mUuid; + private String mKeyOrPkcs12Password; + private String mAuthPassword; + + private PasswordCache(UUID uuid) { + mUuid = uuid; + } + + public static PasswordCache getInstance(UUID uuid) { + if (mInstance == null || !mInstance.mUuid.equals(uuid)) { + mInstance = new PasswordCache(uuid); + } + return mInstance; + } + + public static String getPKCS12orCertificatePassword(UUID uuid, boolean resetPw) { + String pwcopy = getInstance(uuid).mKeyOrPkcs12Password; + if (resetPw) + getInstance(uuid).mKeyOrPkcs12Password = null; + return pwcopy; + } + + + public static String getAuthPassword(UUID uuid, boolean resetPW) { + String pwcopy = getInstance(uuid).mAuthPassword; + if (resetPW) + getInstance(uuid).mAuthPassword = null; + return pwcopy; + } + + public static void setCachedPassword(String uuid, int type, String password) { + PasswordCache instance = getInstance(UUID.fromString(uuid)); + switch (type) { + case PCKS12ORCERTPASSWORD: + instance.mKeyOrPkcs12Password = password; + break; + case AUTHPASSWORD: + instance.mAuthPassword = password; + break; + } + } + + +} diff --git a/app/src/main/java/de/blinkt/openvpn/core/Preferences.java b/app/src/main/java/de/blinkt/openvpn/core/Preferences.java new file mode 100644 index 00000000..76a064ff --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/core/Preferences.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2012-2016 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.content.SharedPreferences; + +/** + * Created by arne on 08.01.17. + */ + +// Until I find a good solution + +public class Preferences { + + static SharedPreferences getSharedPreferencesMulti(String name, Context c) { + return c.getSharedPreferences(name, Context.MODE_MULTI_PROCESS | Context.MODE_PRIVATE); + + } + + + public static SharedPreferences getDefaultSharedPreferences(Context c) { + return c.getSharedPreferences(c.getPackageName() + "_preferences", Context.MODE_MULTI_PROCESS | Context.MODE_PRIVATE); + + } + + +} 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 4f9c219b..f776fc2e 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/ProfileManager.java +++ b/app/src/main/java/de/blinkt/openvpn/core/ProfileManager.java @@ -9,7 +9,6 @@ import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; -import android.preference.PreferenceManager; import java.io.IOException; import java.io.ObjectInputStream; @@ -17,7 +16,9 @@ import java.io.ObjectOutputStream; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.Locale; import java.util.Set; +import java.util.UUID; import de.blinkt.openvpn.VpnProfile; @@ -25,6 +26,7 @@ public class ProfileManager { private static final String PREFS_NAME = "VPNList"; private static final String LAST_CONNECTED_PROFILE = "lastConnectedProfile"; + private static final String TEMPORARY_PROFILE_FILENAME = "temporary-vpn-profile"; private static ProfileManager instance; private static VpnProfile mLastConnectedVpn = null; @@ -59,30 +61,31 @@ public class ProfileManager { } public static void setConntectedVpnProfileDisconnected(Context c) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); + SharedPreferences prefs = Preferences.getDefaultSharedPreferences(c); Editor prefsedit = prefs.edit(); prefsedit.putString(LAST_CONNECTED_PROFILE, null); prefsedit.apply(); } - public static void setConnectedVpnProfile(Context c, VpnProfile connectedrofile) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); + /** + * Sets the profile that is connected (to connect if the service restarts) + */ + public static void setConnectedVpnProfile(Context c, VpnProfile connectedProfile) { + SharedPreferences prefs = Preferences.getDefaultSharedPreferences(c); Editor prefsedit = prefs.edit(); - prefsedit.putString(LAST_CONNECTED_PROFILE, connectedrofile.getUUIDString()); + prefsedit.putString(LAST_CONNECTED_PROFILE, connectedProfile.getUUIDString()); prefsedit.apply(); - mLastConnectedVpn = connectedrofile; + mLastConnectedVpn = connectedProfile; } - public static VpnProfile getLastConnectedProfile(Context c, boolean onBoot) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); - - boolean useStartOnBoot = prefs.getBoolean("restartvpnonboot", false); - - if (onBoot && !useStartOnBoot) - return null; + /** + * Returns the profile that was last connected (to connect if the service restarts) + */ + public static VpnProfile getLastConnectedProfile(Context c) { + SharedPreferences prefs = Preferences.getDefaultSharedPreferences(c); String lastConnectedProfile = prefs.getString(LAST_CONNECTED_PROFILE, null); if (lastConnectedProfile != null) @@ -106,7 +109,7 @@ public class ProfileManager { } public void saveProfileList(Context context) { - SharedPreferences sharedprefs = context.getSharedPreferences(PREFS_NAME, Activity.MODE_PRIVATE); + SharedPreferences sharedprefs = Preferences.getSharedPreferencesMulti(PREFS_NAME, context); Editor editor = sharedprefs.edit(); editor.putStringSet("vpnlist", profiles.keySet()); @@ -124,24 +127,35 @@ public class ProfileManager { } - public static void setTemporaryProfile(VpnProfile tmp) { + public static void setTemporaryProfile(Context c, VpnProfile tmp) { ProfileManager.tmpprofile = tmp; + saveProfile(c, tmp, true, true); } - public static boolean isTempProfile() - { - return mLastConnectedVpn == tmpprofile; + public static boolean isTempProfile() { + return mLastConnectedVpn != null && mLastConnectedVpn == tmpprofile; } - public void saveProfile(Context context, VpnProfile profile) { - ObjectOutputStream vpnfile; + saveProfile(context, profile, true, false); + } + + private static void saveProfile(Context context, VpnProfile profile, boolean updateVersion, boolean isTemporary) { + + if (updateVersion) + profile.mVersion += 1; + ObjectOutputStream vpnFile; + + String filename = profile.getUUID().toString() + ".vp"; + if (isTemporary) + filename = TEMPORARY_PROFILE_FILENAME + ".vp"; + try { - vpnfile = new ObjectOutputStream(context.openFileOutput((profile.getUUID().toString() + ".vp"), Activity.MODE_PRIVATE)); + vpnFile = new ObjectOutputStream(context.openFileOutput(filename, Activity.MODE_PRIVATE)); - vpnfile.writeObject(profile); - vpnfile.flush(); - vpnfile.close(); + vpnFile.writeObject(profile); + vpnFile.flush(); + vpnFile.close(); } catch (IOException e) { VpnStatus.logException("saving VPN profile", e); throw new RuntimeException(e); @@ -151,11 +165,13 @@ public class ProfileManager { private void loadVPNList(Context context) { profiles = new HashMap<>(); - SharedPreferences listpref = context.getSharedPreferences(PREFS_NAME, Activity.MODE_PRIVATE); + SharedPreferences listpref = Preferences.getSharedPreferencesMulti(PREFS_NAME, context); Set vlist = listpref.getStringSet("vpnlist", null); if (vlist == null) { vlist = new HashSet<>(); } + // Always try to load the temporary profile + vlist.add(TEMPORARY_PROFILE_FILENAME); for (String vpnentry : vlist) { try { @@ -167,10 +183,15 @@ public class ProfileManager { continue; vp.upgradeProfile(); - profiles.put(vp.getUUID().toString(), vp); + if (vpnentry.equals(TEMPORARY_PROFILE_FILENAME)) { + tmpprofile = vp; + } else { + profiles.put(vp.getUUID().toString(), vp); + } } catch (IOException | ClassNotFoundException e) { - VpnStatus.logException("Loading VPN List", e); + if (!vpnentry.equals(TEMPORARY_PROFILE_FILENAME)) + VpnStatus.logException("Loading VPN List", e); } } } @@ -187,12 +208,49 @@ public class ProfileManager { } public static VpnProfile get(Context context, String profileUUID) { + return get(context, profileUUID, 0, 10); + } + + public static VpnProfile get(Context context, String profileUUID, int version, int tries) { checkInstance(context); - return get(profileUUID); + VpnProfile profile = get(profileUUID); + int tried = 0; + while ((profile == null || profile.mVersion < version) && (tried++ < tries)) { + try { + Thread.sleep(100); + } catch (InterruptedException ignored) { + } + instance.loadVPNList(context); + profile = get(profileUUID); + int ver = profile == null ? -1 : profile.mVersion; + } + + if (tried > 5) + + { + int ver = profile == null ? -1 : profile.mVersion; + VpnStatus.logError(String.format(Locale.US, "Used x %d tries to get current version (%d/%d) of the profile", tried, ver, version)); + } + return profile; } public static VpnProfile getLastConnectedVpn() { return mLastConnectedVpn; } + public static VpnProfile getAlwaysOnVPN(Context context) { + checkInstance(context); + SharedPreferences prefs = Preferences.getDefaultSharedPreferences(context); + + String uuid = prefs.getString("alwaysOnVpn", null); + return get(uuid); + + } + + public static void updateLRU(Context c, VpnProfile profile) { + profile.mLastUsed = System.currentTimeMillis(); + // LRU does not change the profile, no need for the service to refresh + if (profile!=tmpprofile) + saveProfile(c, profile, false, false); + } } diff --git a/app/src/main/java/de/blinkt/openvpn/core/StatusListener.java b/app/src/main/java/de/blinkt/openvpn/core/StatusListener.java new file mode 100644 index 00000000..5d0b7037 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/core/StatusListener.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2012-2016 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.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; + +import java.io.DataInputStream; +import java.io.File; +import java.io.IOException; + +/** + * Created by arne on 09.11.16. + */ + +public class StatusListener { + private File mCacheDir; + private ServiceConnection mConnection = new ServiceConnection() { + + + @Override + public void onServiceConnected(ComponentName className, + IBinder service) { + // We've bound to LocalService, cast the IBinder and get LocalService instance + IServiceStatus serviceStatus = IServiceStatus.Stub.asInterface(service); + try { + /* Check if this a local service ... */ + if (service.queryLocalInterface("de.blinkt.openvpn.core.IServiceStatus") == null) { + // Not a local service + VpnStatus.setConnectedVPNProfile(serviceStatus.getLastConnectedVPN()); + VpnStatus.setTrafficHistory(serviceStatus.getTrafficHistory()); + ParcelFileDescriptor pfd = serviceStatus.registerStatusCallback(mCallback); + DataInputStream fd = new DataInputStream(new ParcelFileDescriptor.AutoCloseInputStream(pfd)); + + short len = fd.readShort(); + byte[] buf = new byte[65336]; + while (len != 0x7fff) { + fd.readFully(buf, 0, len); + LogItem logitem = new LogItem(buf, len); + VpnStatus.newLogItem(logitem, false); + len = fd.readShort(); + } + fd.close(); + + + + } else { + VpnStatus.initLogCache(mCacheDir); + } + + } catch (RemoteException | IOException e) { + e.printStackTrace(); + VpnStatus.logException(e); + } + } + + @Override + public void onServiceDisconnected(ComponentName arg0) { + + } + + }; + + void init(Context c) { + + Intent intent = new Intent(c, OpenVPNStatusService.class); + intent.setAction(OpenVPNService.START_SERVICE); + mCacheDir = c.getCacheDir(); + + c.bindService(intent, mConnection, Context.BIND_AUTO_CREATE); + + + } + + + private IStatusCallbacks mCallback = new IStatusCallbacks.Stub() + + { + @Override + public void newLogItem(LogItem item) throws RemoteException { + VpnStatus.newLogItem(item); + } + + @Override + public void updateStateString(String state, String msg, int resid, ConnectionStatus + level) throws RemoteException { + VpnStatus.updateStateString(state, msg, resid, level); + } + + @Override + public void updateByteCount(long inBytes, long outBytes) throws RemoteException { + VpnStatus.updateByteCount(inBytes, outBytes); + } + + @Override + public void connectedVPN(String uuid) throws RemoteException { + VpnStatus.setConnectedVPNProfile(uuid); + } + }; + +} diff --git a/app/src/main/java/de/blinkt/openvpn/core/TrafficHistory.java b/app/src/main/java/de/blinkt/openvpn/core/TrafficHistory.java new file mode 100644 index 00000000..6ba35066 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/core/TrafficHistory.java @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2012-2017 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.os.Parcel; +import android.os.Parcelable; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Vector; + +import static java.lang.Math.max; + +/** + * Created by arne on 23.05.17. + */ + +public class TrafficHistory implements Parcelable { + + public static final long PERIODS_TO_KEEP = 5; + public static final int TIME_PERIOD_MINTUES = 60 * 1000; + public static final int TIME_PERIOD_HOURS = 3600 * 1000; + private LinkedList trafficHistorySeconds = new LinkedList<>(); + private LinkedList trafficHistoryMinutes = new LinkedList<>(); + private LinkedList trafficHistoryHours = new LinkedList<>(); + + private TrafficDatapoint lastSecondUsedForMinute; + private TrafficDatapoint lastMinuteUsedForHours; + + public TrafficHistory() { + + } + + protected TrafficHistory(Parcel in) { + in.readList(trafficHistorySeconds, getClass().getClassLoader()); + in.readList(trafficHistoryMinutes, getClass().getClassLoader()); + in.readList(trafficHistoryHours, getClass().getClassLoader()); + lastSecondUsedForMinute = in.readParcelable(getClass().getClassLoader()); + lastMinuteUsedForHours = in.readParcelable(getClass().getClassLoader()); + } + + public static final Creator CREATOR = new Creator() { + @Override + public TrafficHistory createFromParcel(Parcel in) { + return new TrafficHistory(in); + } + + @Override + public TrafficHistory[] newArray(int size) { + return new TrafficHistory[size]; + } + }; + + public LastDiff getLastDiff(TrafficDatapoint tdp) { + + TrafficDatapoint lasttdp; + + + if (trafficHistorySeconds.size() == 0) + lasttdp = new TrafficDatapoint(0, 0, System.currentTimeMillis()); + + else + lasttdp = trafficHistorySeconds.getLast(); + + if (tdp == null) { + tdp = lasttdp; + if (trafficHistorySeconds.size() < 2) + lasttdp = tdp; + else { + trafficHistorySeconds.descendingIterator().next(); + tdp = trafficHistorySeconds.descendingIterator().next(); + } + } + + return new LastDiff(lasttdp, tdp); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeList(trafficHistorySeconds); + dest.writeList(trafficHistoryMinutes); + dest.writeList(trafficHistoryHours); + dest.writeParcelable(lastSecondUsedForMinute, 0); + dest.writeParcelable(lastMinuteUsedForHours, 0); + + } + + public LinkedList getHours() { + return trafficHistoryHours; + } + + public LinkedList getMinutes() { + return trafficHistoryMinutes; + } + + public LinkedList getSeconds() { + return trafficHistorySeconds; + } + + public static LinkedList getDummyList() { + LinkedList list = new LinkedList<>(); + list.add(new TrafficDatapoint(0, 0, System.currentTimeMillis())); + return list; + } + + + public static class TrafficDatapoint implements Parcelable { + private TrafficDatapoint(long inBytes, long outBytes, long timestamp) { + this.in = inBytes; + this.out = outBytes; + this.timestamp = timestamp; + } + + public final long timestamp; + public final long in; + public final long out; + + private TrafficDatapoint(Parcel in) { + timestamp = in.readLong(); + this.in = in.readLong(); + out = in.readLong(); + } + + public static final Creator CREATOR = new Creator() { + @Override + public TrafficDatapoint createFromParcel(Parcel in) { + return new TrafficDatapoint(in); + } + + @Override + public TrafficDatapoint[] newArray(int size) { + return new TrafficDatapoint[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(timestamp); + dest.writeLong(in); + dest.writeLong(out); + } + } + + LastDiff add(long in, long out) { + TrafficDatapoint tdp = new TrafficDatapoint(in, out, System.currentTimeMillis()); + + LastDiff diff = getLastDiff(tdp); + addDataPoint(tdp); + return diff; + } + + private void addDataPoint(TrafficDatapoint tdp) { + trafficHistorySeconds.add(tdp); + + if (lastSecondUsedForMinute == null) { + lastSecondUsedForMinute = new TrafficDatapoint(0, 0, 0); + lastMinuteUsedForHours = new TrafficDatapoint(0, 0, 0); + } + + removeAndAverage(tdp, true); + } + + private void removeAndAverage(TrafficDatapoint newTdp, boolean seconds) { + HashSet toRemove = new HashSet<>(); + Vector toAverage = new Vector<>(); + + long timePeriod; + LinkedList tpList, nextList; + TrafficDatapoint lastTsPeriod; + + if (seconds) { + timePeriod = TIME_PERIOD_MINTUES; + tpList = trafficHistorySeconds; + nextList = trafficHistoryMinutes; + lastTsPeriod = lastSecondUsedForMinute; + } else { + timePeriod = TIME_PERIOD_HOURS; + tpList = trafficHistoryMinutes; + nextList = trafficHistoryHours; + lastTsPeriod = lastMinuteUsedForHours; + } + + if (newTdp.timestamp / timePeriod > (lastTsPeriod.timestamp / timePeriod)) { + nextList.add(newTdp); + + if (seconds) { + lastSecondUsedForMinute = newTdp; + removeAndAverage(newTdp, false); + } else + lastMinuteUsedForHours = newTdp; + + for (TrafficDatapoint tph : tpList) { + // List is iteratered from oldest to newest, remembert first one that we did not + if ((newTdp.timestamp - tph.timestamp) / timePeriod >= PERIODS_TO_KEEP) + toRemove.add(tph); + } + tpList.removeAll(toRemove); + } + } + + static class LastDiff { + + final private TrafficDatapoint tdp; + final private TrafficDatapoint lasttdp; + + private LastDiff(TrafficDatapoint lasttdp, TrafficDatapoint tdp) { + this.lasttdp = lasttdp; + this.tdp = tdp; + } + + public long getDiffOut() { + return max(0, tdp.out - lasttdp.out); + } + + public long getDiffIn() { + return max(0, tdp.in - lasttdp.in); + } + + public long getIn() { + return tdp.in; + } + + public long getOut() { + return tdp.out; + } + + } + + +} \ No newline at end of file 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 78f462e7..f3b40381 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java +++ b/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java @@ -6,6 +6,7 @@ package de.blinkt.openvpn.core; import android.annotation.TargetApi; +import android.app.NotificationManager; import android.content.Context; import android.content.Intent; import android.os.Build; @@ -26,7 +27,6 @@ public class VPNLaunchHelper { private static final String OVPNCONFIGFILE = "android.conf"; - private static String writeMiniVPN(Context context) { String[] abis; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) @@ -38,36 +38,34 @@ public class VPNLaunchHelper { String nativeAPI = NativeUtils.getNativeAPI(); if (!nativeAPI.equals(abis[0])) { VpnStatus.logWarning(R.string.abi_mismatch, Arrays.toString(abis), nativeAPI); - abis = new String[] {nativeAPI}; + abis = new String[]{nativeAPI}; } - for (String abi: abis) { + for (String abi : abis) { - File vpnExecutable = new File(context.getCacheDir(), getMiniVPNExecutableName() + "." + abi); + File vpnExecutable = new File(context.getCacheDir(), "c_" + getMiniVPNExecutableName() + "." + abi); if ((vpnExecutable.exists() && vpnExecutable.canExecute()) || writeMiniVPNBinary(context, abi, vpnExecutable)) { return vpnExecutable.getPath(); } } return null; - } + } @TargetApi(Build.VERSION_CODES.LOLLIPOP) private static String[] getSupportedABIsLollipop() { return Build.SUPPORTED_ABIS; } - private static String getMiniVPNExecutableName() - { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) + private static String getMiniVPNExecutableName() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) return MINIPIEVPN; else return MININONPIEVPN; } - public static String[] replacePieWithNoPie(String[] mArgv) - { + public static String[] replacePieWithNoPie(String[] mArgv) { mArgv[0] = mArgv[0].replace(MINIPIEVPN, MININONPIEVPN); return mArgv; } @@ -79,7 +77,7 @@ public class VPNLaunchHelper { String binaryName = writeMiniVPN(c); // Add fixed paramenters //args.add("/data/data/de.blinkt.openvpn/lib/openvpn"); - if(binaryName==null) { + if (binaryName == null) { VpnStatus.logError("Error writing minivpn binary"); return null; } @@ -98,8 +96,7 @@ public class VPNLaunchHelper { try { mvpn = context.getAssets().open(getMiniVPNExecutableName() + "." + abi); - } - catch (IOException errabi) { + } catch (IOException errabi) { VpnStatus.logInfo("Failed getting assets for archicture " + abi); return false; } @@ -107,16 +104,16 @@ public class VPNLaunchHelper { FileOutputStream fout = new FileOutputStream(mvpnout); - byte buf[]= new byte[4096]; + byte buf[] = new byte[4096]; int lenread = mvpn.read(buf); - while(lenread> 0) { + while (lenread > 0) { fout.write(buf, 0, lenread); lenread = mvpn.read(buf); } fout.close(); - if(!mvpnout.setExecutable(true)) { + if (!mvpnout.setExecutable(true)) { VpnStatus.logError("Failed to make OpenVPN executable"); return false; } @@ -129,14 +126,20 @@ public class VPNLaunchHelper { } } - - public static void startOpenVpn(VpnProfile startprofile, Context context) { - Intent startVPN = startprofile.prepareStartService(context); - if(startVPN!=null) - context.startService(startVPN); - } + public static void startOpenVpn(VpnProfile startprofile, Context context) { + Intent startVPN = startprofile.prepareStartService(context); + if (startVPN != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + //noinspection NewApi + context.startForegroundService(startVPN); + else + context.startService(startVPN); + + } + } + public static String getConfigFilePath(Context context) { return context.getCacheDir().getAbsolutePath() + "/" + OVPNCONFIGFILE; 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 1e2ccba3..a9cc4f18 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java +++ b/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java @@ -5,45 +5,27 @@ package de.blinkt.openvpn.core; -import android.annotation.SuppressLint; import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.Signature; import android.os.Build; import android.os.HandlerThread; import android.os.Message; -import android.os.Parcel; -import android.os.Parcelable; -import android.text.TextUtils; -import android.util.Log; -import java.io.ByteArrayInputStream; -import java.io.DataOutputStream; import java.io.File; -import java.io.OutputStream; import java.io.PrintWriter; import java.io.StringWriter; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.util.Arrays; -import java.util.FormatFlagsConversionMismatchException; import java.util.LinkedList; import java.util.Locale; -import java.util.UnknownFormatConversionException; +import java.util.Queue; import java.util.Vector; +import java.util.concurrent.ConcurrentLinkedQueue; -import se.leap.bitmaskclient.BuildConfig; import se.leap.bitmaskclient.R; +import de.blinkt.openvpn.VpnProfile; public class VpnStatus { - public static LinkedList logbuffer; + private static final LinkedList logbuffer; private static Vector logListener; private static Vector stateListener; @@ -55,7 +37,14 @@ public class VpnStatus { private static int mLastStateresid = R.string.state_noprocess; - private static long mlastByteCount[] = {0, 0, 0, 0}; + private static HandlerThread mHandlerThread; + + private static String mLastConnectedVPNUUID; + static boolean readFileLog =false; + final static java.lang.Object readFileLock = new Object(); + + + public static TrafficHistory trafficHistory; public static void logException(LogLevel ll, String context, Exception e) { StringWriter sw = new StringWriter(); @@ -79,6 +68,9 @@ public class VpnStatus { static final int MAXLOGENTRIES = 1000; + public static boolean isVPNActive() { + return mLastLevel != ConnectionStatus.LEVEL_AUTH_FAILED && !(mLastLevel == ConnectionStatus.LEVEL_NOTCONNECTED); + } public static String getLastCleanLogMessage(Context c) { String message = mLaststatemsg; @@ -111,6 +103,10 @@ public class VpnStatus { if (status.equals("NOPROCESS")) return message; + if (mLastStateresid == R.string.state_waitconnectretry) { + return c.getString(R.string.state_waitconnectretry, mLaststatemsg); + } + String prefix = c.getString(mLastStateresid); if (mLastStateresid == R.string.unknown_state) message = status + message; @@ -122,28 +118,38 @@ public class VpnStatus { } public static void initLogCache(File cacheDir) { + mHandlerThread = new HandlerThread("LogFileWriter", Thread.MIN_PRIORITY); + mHandlerThread.start(); + mLogFileHandler = new LogFileHandler(mHandlerThread.getLooper()); + + Message m = mLogFileHandler.obtainMessage(LogFileHandler.LOG_INIT, cacheDir); mLogFileHandler.sendMessage(m); } public static void flushLog() { - mLogFileHandler.sendEmptyMessage(LogFileHandler.FLUSH_TO_DISK); + if (mLogFileHandler!=null) + mLogFileHandler.sendEmptyMessage(LogFileHandler.FLUSH_TO_DISK); } - public enum ConnectionStatus { - LEVEL_CONNECTED, - LEVEL_VPNPAUSED, - LEVEL_CONNECTING_SERVER_REPLIED, - LEVEL_CONNECTING_NO_SERVER_REPLY_YET, - LEVEL_NONETWORK, - LEVEL_NOTCONNECTED, - LEVEL_START, - LEVEL_AUTH_FAILED, - LEVEL_WAITING_FOR_USER_INPUT, - UNKNOWN_LEVEL + public static void setConnectedVPNProfile(String uuid) { + mLastConnectedVPNUUID = uuid; + for (StateListener sl: stateListener) + sl.setConnectedVPN(uuid); } + + public static String getLastConnectedVPNProfile() + { + return mLastConnectedVPNUUID; + } + + public static void setTrafficHistory(TrafficHistory trafficHistory) { + VpnStatus.trafficHistory = trafficHistory; + } + + public enum LogLevel { INFO(2), ERROR(-2), @@ -163,14 +169,17 @@ public class VpnStatus { public static LogLevel getEnumByValue(int value) { switch (value) { - case 1: - return INFO; case 2: + return INFO; + case -2: return ERROR; - case 3: + case 1: return WARNING; + case 3: + return VERBOSE; case 4: return DEBUG; + default: return null; } @@ -178,218 +187,36 @@ public class VpnStatus { } // keytool -printcert -jarfile de.blinkt.openvpn_85.apk - public static final byte[] officalkey = {-58, -42, -44, -106, 90, -88, -87, -88, -52, -124, 84, 117, 66, 79, -112, -111, -46, 86, -37, 109}; - public static final byte[] officaldebugkey = {-99, -69, 45, 71, 114, -116, 82, 66, -99, -122, 50, -70, -56, -111, 98, -35, -65, 105, 82, 43}; - public static final byte[] amazonkey = {-116, -115, -118, -89, -116, -112, 120, 55, 79, -8, -119, -23, 106, -114, -85, -56, -4, 105, 26, -57}; - public static final byte[] fdroidkey = {-92, 111, -42, -46, 123, -96, -60, 79, -27, -31, 49, 103, 11, -54, -68, -27, 17, 2, 121, 104}; + static final byte[] officalkey = {-58, -42, -44, -106, 90, -88, -87, -88, -52, -124, 84, 117, 66, 79, -112, -111, -46, 86, -37, 109}; + static final byte[] officaldebugkey = {-99, -69, 45, 71, 114, -116, 82, 66, -99, -122, 50, -70, -56, -111, 98, -35, -65, 105, 82, 43}; + static final byte[] amazonkey = {-116, -115, -118, -89, -116, -112, 120, 55, 79, -8, -119, -23, 106, -114, -85, -56, -4, 105, 26, -57}; + static final byte[] fdroidkey = {-92, 111, -42, -46, 123, -96, -60, 79, -27, -31, 49, 103, 11, -54, -68, -27, 17, 2, 121, 104}; private static ConnectionStatus mLastLevel = ConnectionStatus.LEVEL_NOTCONNECTED; - private static final LogFileHandler mLogFileHandler; + private static LogFileHandler mLogFileHandler; static { logbuffer = new LinkedList<>(); logListener = new Vector<>(); stateListener = new Vector<>(); byteCountListener = new Vector<>(); - - HandlerThread mHandlerThread = new HandlerThread("LogFileWriter", Thread.MIN_PRIORITY); - mHandlerThread.start(); - mLogFileHandler = new LogFileHandler(mHandlerThread.getLooper()); + trafficHistory = new TrafficHistory(); logInformation(); } - public static class LogItem implements Parcelable { - private Object[] mArgs = null; - private String mMessage = null; - private int mRessourceId; - // Default log priority - LogLevel mLevel = LogLevel.INFO; - private long logtime = System.currentTimeMillis(); - private int mVerbosityLevel = -1; - - private LogItem(int ressourceId, Object[] args) { - mRessourceId = ressourceId; - mArgs = args; - } - - public LogItem(LogLevel level, int verblevel, String message) { - mMessage = message; - mLevel = level; - mVerbosityLevel = verblevel; - } - - @Override - public int describeContents() { - return 0; - } - - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeArray(mArgs); - dest.writeString(mMessage); - dest.writeInt(mRessourceId); - dest.writeInt(mLevel.getInt()); - dest.writeInt(mVerbosityLevel); - - dest.writeLong(logtime); - } - - public LogItem(Parcel in) { - mArgs = in.readArray(Object.class.getClassLoader()); - mMessage = in.readString(); - mRessourceId = in.readInt(); - mLevel = LogLevel.getEnumByValue(in.readInt()); - mVerbosityLevel = in.readInt(); - logtime = in.readLong(); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - public LogItem createFromParcel(Parcel in) { - return new LogItem(in); - } - - public LogItem[] newArray(int size) { - return new LogItem[size]; - } - }; - - public LogItem(LogLevel loglevel, int ressourceId, Object... args) { - mRessourceId = ressourceId; - mArgs = args; - mLevel = loglevel; - } - - - public LogItem(LogLevel loglevel, String msg) { - mLevel = loglevel; - mMessage = msg; - } - - - public LogItem(LogLevel loglevel, int ressourceId) { - mRessourceId = ressourceId; - mLevel = loglevel; - } - - public String getString(Context c) { - try { - if (mMessage != null) { - return mMessage; - } else { - if (c != null) { - if (mRessourceId == R.string.mobile_info) - return getMobileInfoString(c); - if (mArgs == null) - return c.getString(mRessourceId); - else - return c.getString(mRessourceId, mArgs); - } else { - String str = String.format(Locale.ENGLISH, "Log (no context) resid %d", mRessourceId); - if (mArgs != null) - str += TextUtils.join("|", mArgs); - - return str; - } - } - } catch (UnknownFormatConversionException e) { - if (c != null) - throw new UnknownFormatConversionException(e.getLocalizedMessage() + getString(null)); - else - throw e; - } catch (java.util.FormatFlagsConversionMismatchException e) { - if (c != null) - throw new FormatFlagsConversionMismatchException(e.getLocalizedMessage() + getString(null), e.getConversion()); - else - throw e; - } - - } - - public LogLevel getLogLevel() { - return mLevel; - } - - // The lint is wrong here - @SuppressLint("StringFormatMatches") - private String getMobileInfoString(Context c) { - c.getPackageManager(); - String apksign = "error getting package signature"; - - String version = "error getting version"; - try { - @SuppressLint("PackageManagerGetSignatures") - Signature raw = c.getPackageManager().getPackageInfo(c.getPackageName(), PackageManager.GET_SIGNATURES).signatures[0]; - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(raw.toByteArray())); - MessageDigest md = MessageDigest.getInstance("SHA-1"); - byte[] der = cert.getEncoded(); - md.update(der); - byte[] digest = md.digest(); - - if (Arrays.equals(digest, officalkey)) - apksign = c.getString(R.string.official_build); - else if (Arrays.equals(digest, officaldebugkey)) - apksign = c.getString(R.string.debug_build); - else if (Arrays.equals(digest, amazonkey)) - apksign = "amazon version"; - else if (Arrays.equals(digest, fdroidkey)) - apksign = "F-Droid built and signed version"; - else - apksign = c.getString(R.string.built_by, cert.getSubjectX500Principal().getName()); - - PackageInfo packageinfo = c.getPackageManager().getPackageInfo(c.getPackageName(), 0); - version = packageinfo.versionName; - - } catch (NameNotFoundException | CertificateException | - NoSuchAlgorithmException ignored) { - } - - Object[] argsext = Arrays.copyOf(mArgs, mArgs.length); - argsext[argsext.length - 1] = apksign; - argsext[argsext.length - 2] = version; - - return c.getString(R.string.mobile_info, argsext); - - } - - public long getLogtime() { - return logtime; - } - - - public int getVerbosityLevel() { - if (mVerbosityLevel == -1) { - // Hack: - // For message not from OpenVPN, report the status level as log level - return mLevel.getInt(); - } - return mVerbosityLevel; - } - - public boolean verify() { - if (mLevel == null) - return false; - - if (mMessage == null && mRessourceId == 0) - return false; - - return true; - } - } - public interface LogListener { void newLog(LogItem logItem); } public interface StateListener { void updateState(String state, String logmessage, int localizedResId, ConnectionStatus level); + + void setConnectedVPN(String uuid); } public interface ByteCountListener { @@ -404,12 +231,20 @@ public class VpnStatus { public synchronized static void clearLog() { logbuffer.clear(); logInformation(); - mLogFileHandler.sendEmptyMessage(LogFileHandler.TRIM_LOG_FILE); + if (mLogFileHandler != null) + mLogFileHandler.sendEmptyMessage(LogFileHandler.TRIM_LOG_FILE); } private static void logInformation() { + String nativeAPI; + try { + nativeAPI = NativeUtils.getNativeAPI(); + } catch (UnsatisfiedLinkError ignore) { + nativeAPI = "error"; + } + logInfo(R.string.mobile_info, Build.MODEL, Build.BOARD, Build.BRAND, Build.VERSION.SDK_INT, - NativeUtils.getNativeAPI(), Build.VERSION.RELEASE, Build.ID, Build.FINGERPRINT, "", ""); + nativeAPI, Build.VERSION.RELEASE, Build.ID, Build.FINGERPRINT, "", ""); } public synchronized static void addLogListener(LogListener ll) { @@ -421,7 +256,8 @@ public class VpnStatus { } public synchronized static void addByteCountListener(ByteCountListener bcl) { - bcl.updateByteCount(mlastByteCount[0], mlastByteCount[1], mlastByteCount[2], mlastByteCount[3]); + TrafficHistory.LastDiff diff = trafficHistory.getLastDiff(null); + bcl.updateByteCount(diff.getIn(), diff.getOut(), diff.getDiffIn(),diff.getDiffOut()); byteCountListener.add(bcl); } @@ -525,7 +361,7 @@ public class VpnStatus { } - public static void updateStateString(String state, String msg) { + static void updateStateString(String state, String msg) { int rid = getLocalizedState(state); ConnectionStatus level = getLevel(state); updateStateString(state, msg, rid, level); @@ -549,7 +385,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) { @@ -568,7 +404,7 @@ public class VpnStatus { newLogItem(new LogItem(LogLevel.DEBUG, resourceId, args)); } - private static void newLogItem(LogItem logItem) { + static void newLogItem(LogItem logItem) { newLogItem(logItem, false); } @@ -578,18 +414,21 @@ public class VpnStatus { logbuffer.addFirst(logItem); } else { logbuffer.addLast(logItem); - Message m = mLogFileHandler.obtainMessage(LogFileHandler.LOG_MESSAGE, logItem); - mLogFileHandler.sendMessage(m); + if (mLogFileHandler != null) { + Message m = mLogFileHandler.obtainMessage(LogFileHandler.LOG_MESSAGE, logItem); + mLogFileHandler.sendMessage(m); + } } if (logbuffer.size() > MAXLOGENTRIES + MAXLOGENTRIES / 2) { while (logbuffer.size() > MAXLOGENTRIES) logbuffer.removeFirst(); - mLogFileHandler.sendMessage(mLogFileHandler.obtainMessage(LogFileHandler.TRIM_LOG_FILE)); + if (mLogFileHandler != null) + mLogFileHandler.sendMessage(mLogFileHandler.obtainMessage(LogFileHandler.TRIM_LOG_FILE)); } - if (BuildConfig.DEBUG && !cachedLine) - Log.d("OpenVPN", logItem.getString(null)); + //if (BuildConfig.DEBUG && !cachedLine && !BuildConfig.FLAVOR.equals("test")) + // Log.d("OpenVPN", logItem.getString(null)); for (LogListener ll : logListener) { @@ -627,17 +466,10 @@ public class VpnStatus { public static synchronized void updateByteCount(long in, long out) { - long lastIn = mlastByteCount[0]; - long lastOut = mlastByteCount[1]; - long diffIn = mlastByteCount[2] = Math.max(0, in - lastIn); - long diffOut = mlastByteCount[3] = Math.max(0, out - lastOut); - + TrafficHistory.LastDiff diff = trafficHistory.add(in, out); - mlastByteCount = new long[]{in, out, diffIn, diffOut}; for (ByteCountListener bcl : byteCountListener) { - bcl.updateByteCount(in, out, diffIn, diffOut); + bcl.updateByteCount(in, out, diff.getDiffIn(), diff.getDiffOut()); } } - - } diff --git a/app/src/main/java/de/blinkt/openvpn/core/X509Utils.java b/app/src/main/java/de/blinkt/openvpn/core/X509Utils.java index 4048f0e0..9e2060fd 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/X509Utils.java +++ b/app/src/main/java/de/blinkt/openvpn/core/X509Utils.java @@ -5,6 +5,7 @@ package de.blinkt.openvpn.core; +import android.annotation.SuppressLint; import android.content.Context; import android.content.res.Resources; import android.text.TextUtils; @@ -106,14 +107,14 @@ public class X509Utils { // More than 3 months display months if (timeLeft > 90l* 24 * 3600 * 1000) { long months = getMonthsDifference(now, certNotAfter); - return res.getString(R.string.months_left, months); + return res.getQuantityString(R.plurals.months_left, (int) months, months); } else if (timeLeft > 72 * 3600 * 1000) { long days = timeLeft / (24 * 3600 * 1000); - return res.getString(R.string.days_left, days); + return res.getQuantityString(R.plurals.days_left, (int) days, days); } else { long hours = timeLeft / (3600 * 1000); - return res.getString(R.string.hours_left, hours); + return res.getQuantityString(R.plurals.hours_left, (int)hours, hours); } } @@ -131,7 +132,7 @@ public class X509Utils { /* Hack so we do not have to ship a whole Spongy/bouncycastle */ Exception exp=null; try { - Class X509NameClass = Class.forName("com.android.org.bouncycastle.asn1.x509.X509Name"); + @SuppressLint("PrivateApi") Class X509NameClass = Class.forName("com.android.org.bouncycastle.asn1.x509.X509Name"); Method getInstance = X509NameClass.getMethod("getInstance",Object.class); Hashtable defaultSymbols = (Hashtable) X509NameClass.getField("DefaultSymbols").get(X509NameClass); diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java b/app/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java index bbd52a34..a1fc7cdc 100644 --- a/app/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java +++ b/app/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java @@ -16,6 +16,7 @@ import android.content.ClipboardManager; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.res.Resources; import android.database.DataSetObserver; import android.graphics.drawable.Drawable; import android.os.Bundle; @@ -56,12 +57,13 @@ import de.blinkt.openvpn.LaunchVPN; import se.leap.bitmaskclient.R; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.activities.DisconnectVPN; +import de.blinkt.openvpn.core.ConnectionStatus; import de.blinkt.openvpn.core.OpenVPNManagement; import de.blinkt.openvpn.core.OpenVPNService; +import de.blinkt.openvpn.core.Preferences; import de.blinkt.openvpn.core.ProfileManager; import de.blinkt.openvpn.core.VpnStatus; -import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus; -import de.blinkt.openvpn.core.VpnStatus.LogItem; +import de.blinkt.openvpn.core.LogItem; import de.blinkt.openvpn.core.VpnStatus.LogListener; import de.blinkt.openvpn.core.VpnStatus.StateListener; @@ -117,8 +119,9 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar. @Override public void updateByteCount(long in, long out, long diffIn, long diffOut) { //%2$s/s %1$s - ↑%4$s/s %3$s - final String down = String.format("%2$s/s %1$s", humanReadableByteCount(in, false), humanReadableByteCount(diffIn / OpenVPNManagement.mBytecountInterval, true)); - final String up = String.format("%2$s/s %1$s", humanReadableByteCount(out, false), humanReadableByteCount(diffOut / OpenVPNManagement.mBytecountInterval, true)); + Resources res = getActivity().getResources(); + final String down = String.format("%2$s %1$s", humanReadableByteCount(in, false, res), humanReadableByteCount(diffIn / OpenVPNManagement.mBytecountInterval, true, res)); + final String up = String.format("%2$s %1$s", humanReadableByteCount(out, false, res), humanReadableByteCount(diffOut / OpenVPNManagement.mBytecountInterval, true, res)); if (mUpStatus != null && mDownStatus != null) { if (getActivity() != null) { @@ -429,33 +432,34 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar. Intent intent = new Intent(getActivity(), DisconnectVPN.class); startActivity(intent); return true; - } else if(item.getItemId()==R.id.send) { - ladapter.shareLog(); - } else if(item.getItemId()==R.id.edit_vpn) { - VpnProfile lastConnectedprofile = ProfileManager.getLastConnectedVpn(); - - if(lastConnectedprofile!=null) { - Intent vprefintent = new Intent(getActivity(),Dashboard.class) - .putExtra(VpnProfile.EXTRA_PROFILEUUID,lastConnectedprofile.getUUIDString()); - startActivityForResult(vprefintent,START_VPN_CONFIG); - } else { - Toast.makeText(getActivity(), R.string.log_no_last_vpn, Toast.LENGTH_LONG).show(); - } - } else if(item.getItemId() == R.id.toggle_time) { - showHideOptionsPanel(); - } else if(item.getItemId() == android.R.id.home) { - // This is called when the Home (Up) button is pressed - // in the Action Bar. - Intent parentActivityIntent = new Intent(getActivity(), Dashboard.class); - parentActivityIntent.addFlags( - Intent.FLAG_ACTIVITY_CLEAR_TOP | - Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(parentActivityIntent); - getActivity().finish(); - return true; - - } - return super.onOptionsItemSelected(item); + } else if (item.getItemId() == R.id.send) { + ladapter.shareLog(); + } else if (item.getItemId() == R.id.edit_vpn) { + VpnProfile lastConnectedprofile = ProfileManager.get(getActivity(), VpnStatus.getLastConnectedVPNProfile()); + + if (lastConnectedprofile != null) { + Intent vprefintent = new Intent(getActivity(), Dashboard.class) + .putExtra(VpnProfile.EXTRA_PROFILEUUID, lastConnectedprofile.getUUIDString()); + startActivityForResult(vprefintent, START_VPN_CONFIG); + } else { + Toast.makeText(getActivity(), R.string.log_no_last_vpn, Toast.LENGTH_LONG).show(); + } + } else if (item.getItemId() == R.id.toggle_time) { + showHideOptionsPanel(); + } else if (item.getItemId() == android.R.id.home) { + // This is called when the Home (Up) button is pressed + // in the Action Bar. + Intent parentActivityIntent = new Intent(getActivity(), Dashboard.class); + parentActivityIntent.addFlags( + Intent.FLAG_ACTIVITY_CLEAR_TOP | + Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(parentActivityIntent); + getActivity().finish(); + return true; + + } + return super.onOptionsItemSelected(item); + } private void showHideOptionsPanel() { @@ -500,13 +504,16 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar. @Override public void onResume() { super.onResume(); - VpnStatus.addStateListener(this); - VpnStatus.addByteCountListener(this); Intent intent = new Intent(getActivity(), OpenVPNService.class); intent.setAction(OpenVPNService.START_SERVICE); - } + @Override + public void onStart() { + super.onStart(); + VpnStatus.addStateListener(this); + VpnStatus.addByteCountListener(this); + } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { @@ -603,7 +610,7 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar. mClearLogCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - PreferenceManager.getDefaultSharedPreferences(getActivity()).edit().putBoolean(LaunchVPN.CLEARLOG, isChecked).apply(); + Preferences.getDefaultSharedPreferences(getActivity()).edit().putBoolean(LaunchVPN.CLEARLOG, isChecked).apply(); } }); @@ -628,7 +635,14 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar. } @Override - public void onAttach(Activity activity) { + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + // Scroll to the end of the list end + //getListView().setSelection(getListView().getAdapter().getCount()-1); + } + + @Override + public void onAttach(Context activity) { super.onAttach(activity); if (getResources().getBoolean(R.bool.logSildersAlwaysVisible)) { mShowOptionsLayout = true; @@ -660,13 +674,17 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar. mSpeedView.setText(cleanLogMessage); } if (mConnectStatus != null) - mConnectStatus.setText(getString(resId)); + mConnectStatus.setText(cleanLogMessage); } } }); } } + @Override + public void setConnectedVPN(String uuid) { + } + @Override public void onDestroy() { diff --git a/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java b/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java index 6ffeacc1..9e9adef1 100644 --- a/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java @@ -19,6 +19,7 @@ package se.leap.bitmaskclient; import android.app.*; import android.content.*; import android.os.*; +import android.util.Log; import android.view.*; import android.widget.*; @@ -39,7 +40,7 @@ public class VpnFragment extends Fragment implements Observer { protected static final String IS_CONNECTED = TAG + ".is_connected"; public static final String START_ON_BOOT = "start on boot"; - @InjectView(R.id.vpn_Status_Image) + @InjectView(R.id.vpn_status_image) FabButton vpn_status_image; @InjectView(R.id.vpn_main_button) Button main_button; @@ -91,7 +92,8 @@ public class VpnFragment extends Fragment implements Observer { @Override public void onResume() { super.onResume(); - eipCommand(Constants.ACTION_CHECK_CERT_VALIDITY); + //FIXME: avoid race conditions while checking certificate an logging in at about the same time + //eipCommand(Constants.ACTION_CHECK_CERT_VALIDITY); handleNewState(eip_status); } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java index 9ff7f1af..5b72a4e7 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java @@ -117,8 +117,8 @@ public final class EIP extends IntentService { Intent intent = new Intent(this, LaunchVPN.class); intent.setAction(Intent.ACTION_MAIN); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(LaunchVPN.EXTRA_NAME, gateway.getProfile().getName()); intent.putExtra(LaunchVPN.EXTRA_HIDELOG, true); + intent.putExtra(LaunchVPN.EXTRA_TEMP_VPN_PROFILE, gateway.getProfile()); startActivity(intent); } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java b/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java index 4bfef1cb..501543b8 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java @@ -26,7 +26,7 @@ public class EipStatus extends Observable implements VpnStatus.StateListener { public static String TAG = EipStatus.class.getSimpleName(); private static EipStatus current_status; - private static VpnStatus.ConnectionStatus level = VpnStatus.ConnectionStatus.LEVEL_NOTCONNECTED; + private static ConnectionStatus level = ConnectionStatus.LEVEL_NOTCONNECTED; private static boolean wants_to_disconnect = false, is_connecting = false; @@ -47,7 +47,7 @@ public class EipStatus extends Observable implements VpnStatus.StateListener { } @Override - public void updateState(final String state, final String logmessage, final int localizedResId, final VpnStatus.ConnectionStatus level) { + public void updateState(final String state, final String logmessage, final int localizedResId, final ConnectionStatus level) { updateStatus(state, logmessage, localizedResId, level); if (isConnected() || isDisconnected() || wantsToDisconnect()) { setConnectedOrDisconnected(); @@ -55,7 +55,11 @@ public class EipStatus extends Observable implements VpnStatus.StateListener { setConnecting(); } - private void updateStatus(final String state, final String logmessage, final int localizedResId, final VpnStatus.ConnectionStatus level) { + @Override + public void setConnectedVPN(String uuid) { + } + + private void updateStatus(final String state, final String logmessage, final int localizedResId, final ConnectionStatus level) { current_status = getInstance(); current_status.setState(state); current_status.setLogMessage(logmessage); @@ -73,15 +77,15 @@ public class EipStatus extends Observable implements VpnStatus.StateListener { } public boolean isConnected() { - return level == VpnStatus.ConnectionStatus.LEVEL_CONNECTED; + return level == ConnectionStatus.LEVEL_CONNECTED; } public boolean isDisconnected() { - return level == VpnStatus.ConnectionStatus.LEVEL_NOTCONNECTED; + return level == ConnectionStatus.LEVEL_NOTCONNECTED; } public boolean isPaused() { - return level == VpnStatus.ConnectionStatus.LEVEL_VPNPAUSED; + return level == ConnectionStatus.LEVEL_VPNPAUSED; } public void setConnecting() { @@ -116,7 +120,7 @@ public class EipStatus extends Observable implements VpnStatus.StateListener { return localized_res_id; } - public VpnStatus.ConnectionStatus getLevel() { + public ConnectionStatus getLevel() { return level; } @@ -132,7 +136,7 @@ public class EipStatus extends Observable implements VpnStatus.StateListener { this.localized_res_id = localized_res_id; } - private void setLevel(VpnStatus.ConnectionStatus level) { + private void setLevel(ConnectionStatus level) { EipStatus.level = level; } @@ -145,13 +149,13 @@ public class EipStatus extends Observable implements VpnStatus.StateListener { String[] error_keywords = {"error", "ERROR", "fatal", "FATAL"}; - VpnStatus.LogItem[] log = VpnStatus.getlogbuffer(); + LogItem[] log = VpnStatus.getlogbuffer(); if(log.length < last_error_line) last_error_line = 0; String message = ""; for (int i = 1; i <= lines && log.length > i; i++) { int line = log.length - i; - VpnStatus.LogItem log_item = log[line]; + LogItem log_item = log[line]; message = log_item.getString(context); for (int j = 0; j < error_keywords.length; j++) if (message.contains(error_keywords[j]) && line > last_error_line) { diff --git a/app/src/main/res/layout-xlarge/eip_service_fragment.xml b/app/src/main/res/layout-xlarge/eip_service_fragment.xml index a9f01fb8..2b3c4f2e 100644 --- a/app/src/main/res/layout-xlarge/eip_service_fragment.xml +++ b/app/src/main/res/layout-xlarge/eip_service_fragment.xml @@ -27,7 +27,7 @@ android:layout_centerInParent="true">