diff options
31 files changed, 710 insertions, 474 deletions
diff --git a/app/build.gradle b/app/build.gradle index 5a1b1217..87726734 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -216,16 +216,20 @@ android { //runProguard true if(signingConfigs.contains(release)) signingConfig signingConfigs.release.isSigningReady() ? signingConfigs.release : signingConfigs.debug + //ndk.debugSymbolLevel = "full" //minifyEnabled = true //shrinkResources true + buildConfigField "Boolean", "DEBUG_MODE", "false" } beta { initWith release applicationIdSuffix ".beta" appSuffix = " Beta" + buildConfigField "Boolean", "DEBUG_MODE", "true" } debug { testCoverageEnabled = true + buildConfigField "Boolean", "DEBUG_MODE", "true" } } @@ -423,7 +427,7 @@ android.applicationVariants.all { variant -> } // remove unrelated abi specific assets - variant.mergeAssets.doLast { + variant.mergeAssetsProvider.get().doLast { // if not a fat build if (abiDimension.abiVersionCode > 0) { def filesToDelete = fileTree(dir: variant.mergeAssets.outputDir, excludes: ["*pie_openvpn.${abiDimension.abiFilter}", diff --git a/app/src/insecure/assets/urls/cdev.bitmask.net.url b/app/src/insecure/assets/urls/cdev.bitmask.net.url deleted file mode 100644 index 4ceca5ee..00000000 --- a/app/src/insecure/assets/urls/cdev.bitmask.net.url +++ /dev/null @@ -1,3 +0,0 @@ -{ - "main_url" : "https://cdev.bitmask.net/" -}
\ No newline at end of file diff --git a/app/src/insecure/assets/urls/dev.bitmask.net.url b/app/src/insecure/assets/urls/dev.bitmask.net.url deleted file mode 100644 index 5d4ae485..00000000 --- a/app/src/insecure/assets/urls/dev.bitmask.net.url +++ /dev/null @@ -1,3 +0,0 @@ -{ - "main_url" : "https://dev.bitmask.net/" -}
\ No newline at end of file diff --git a/app/src/insecure/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java b/app/src/insecure/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java index a13f056f..833d1e48 100644 --- a/app/src/insecure/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java +++ b/app/src/insecure/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java @@ -19,6 +19,7 @@ package se.leap.bitmaskclient.providersetup; import android.content.SharedPreferences; import android.content.res.Resources; +import static se.leap.bitmaskclient.BuildConfig.DEBUG_MODE; import android.os.Bundle; import android.util.Pair; @@ -44,6 +45,7 @@ import javax.net.ssl.SSLSession; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; +import de.blinkt.openvpn.core.VpnStatus; import okhttp3.OkHttpClient; import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.base.models.Provider; @@ -104,6 +106,7 @@ public class ProviderApiManager extends ProviderApiManagerBase { if (isEmpty(provider.getMainUrlString()) || provider.getMainUrl().isDefault()) { setErrorResult(currentDownload, malformed_url, null); currentDownload.putParcelable(PROVIDER_KEY, provider); + VpnStatus.logWarning("[API] MainURL String is not set. Cannot setup provider."); return currentDownload; } @@ -153,6 +156,10 @@ public class ProviderApiManager extends ProviderApiManagerBase { return result; } + if (DEBUG_MODE) { + VpnStatus.logDebug("[API] PROVIDER JSON: " + providerDotJsonString); + } + try { JSONObject providerJson = new JSONObject(providerDotJsonString); @@ -183,6 +190,9 @@ public class ProviderApiManager extends ProviderApiManagerBase { JSONObject providerDefinition = provider.getDefinition(); String eipServiceUrl = providerDefinition.getString(Provider.API_URL) + "/" + providerDefinition.getString(Provider.API_VERSION) + "/" + EIP.SERVICE_API_PATH; eipServiceJsonString = downloadWithProviderCA(provider.getCaCert(), eipServiceUrl, lastDangerOn); + if (DEBUG_MODE) { + VpnStatus.logDebug("[API] EIP SERVICE JSON: " + eipServiceJsonString); + } JSONObject eipServiceJson = new JSONObject(eipServiceJsonString); @@ -213,6 +223,9 @@ public class ProviderApiManager extends ProviderApiManagerBase { URL newCertStringUrl = new URL(provider.getApiUrlWithVersion() + "/" + PROVIDER_VPN_CERTIFICATE); String certString = downloadWithProviderCA(provider.getCaCert(), newCertStringUrl.toString(), lastDangerOn); + if (DEBUG_MODE) { + VpnStatus.logDebug("[API] VPN CERT: " + certString); + } if (ConfigHelper.checkErroneousDownload(certString)) { if (certString == null || certString.isEmpty()) { // probably 204 @@ -252,6 +265,9 @@ public class ProviderApiManager extends ProviderApiManagerBase { URL geoIpUrl = provider.getGeoipUrl().getUrl(); String geoipJsonString = downloadFromUrlWithProviderCA(geoIpUrl.toString(), provider, lastDangerOn); + if (DEBUG_MODE) { + VpnStatus.logDebug("[API] MENSHEN JSON: " + geoipJsonString); + } JSONObject geoipJson = new JSONObject(geoipJsonString); if (geoipJson.has(ERRORS)) { @@ -281,6 +297,9 @@ public class ProviderApiManager extends ProviderApiManagerBase { if (validCertificate(provider, certString)) { provider.setCaCert(certString); + if (DEBUG_MODE) { + VpnStatus.logDebug("[API] CA CERT: " + certString); + } preferences.edit().putString(Provider.CA_CERT + "." + providerDomain, certString).apply(); result.putBoolean(BROADCAST_RESULT_KEY, true); } else { diff --git a/app/src/main/aidl/de/blinkt/openvpn/core/IOpenVPNServiceInternal.aidl b/app/src/main/aidl/de/blinkt/openvpn/core/IOpenVPNServiceInternal.aidl index 293c2b6d..98cc3e42 100644 --- a/app/src/main/aidl/de/blinkt/openvpn/core/IOpenVPNServiceInternal.aidl +++ b/app/src/main/aidl/de/blinkt/openvpn/core/IOpenVPNServiceInternal.aidl @@ -23,4 +23,6 @@ interface IOpenVPNServiceInternal { boolean isVpnRunning(); + void startWithForegroundNotification(); + } diff --git a/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java b/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java index b148d04d..b477f6d5 100644 --- a/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java +++ b/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java @@ -5,29 +5,25 @@ package de.blinkt.openvpn; -import android.annotation.TargetApi; import android.app.Activity; -import android.app.AlertDialog; import android.content.ActivityNotFoundException; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; import android.content.Intent; -import android.content.SharedPreferences; import android.net.VpnService; import android.os.Build; import android.os.Bundle; -import java.io.IOException; +import androidx.annotation.StringRes; import de.blinkt.openvpn.core.ConnectionStatus; -import de.blinkt.openvpn.core.Preferences; -import de.blinkt.openvpn.core.VPNLaunchHelper; import de.blinkt.openvpn.core.VpnStatus; -import se.leap.bitmaskclient.base.MainActivity; import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.eip.EipCommand; +import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_PREPARE_VPN; +import static se.leap.bitmaskclient.base.models.Constants.EIP_N_CLOSEST_GATEWAY; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PROFILE; +import static se.leap.bitmaskclient.eip.EIP.ERRORS; import static se.leap.bitmaskclient.eip.EipResultBroadcast.tellToReceiverOrBroadcast; /** @@ -57,54 +53,53 @@ import static se.leap.bitmaskclient.eip.EipResultBroadcast.tellToReceiverOrBroad */ public class LaunchVPN extends Activity { - public static final String EXTRA_KEY = "de.blinkt.openvpn.shortcutProfileUUID"; - 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"; - - private static final int START_VPN_PROFILE = 70; private static final String TAG = LaunchVPN.class.getName(); + private VpnProfile selectedProfile; + private int selectedGateway; - private VpnProfile mSelectedProfile; - private boolean mhideLog = false; - - private boolean mCmfixed = false; - @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); - startVpnFromIntent(); - } - - protected void startVpnFromIntent() { - // Resolve the intent - final Intent intent = getIntent(); final String action = intent.getAction(); + if (!Intent.ACTION_MAIN.equals(action)) { + finish(); + } - // If the intent is a request to create a shortcut, we'll do that and exit - + VpnProfile profileToConnect = (VpnProfile) intent.getExtras().getSerializable(PROVIDER_PROFILE); + selectedGateway = intent.getExtras().getInt(EIP_N_CLOSEST_GATEWAY, 0); + if (profileToConnect == null) { + showAlertInMainActivity(R.string.shortcut_profile_notfound); + finish(); + } else { + selectedProfile = profileToConnect; + } - if (Intent.ACTION_MAIN.equals(action)) { - // Check if we need to clear the log - if (Preferences.getDefaultSharedPreferences(this).getBoolean(CLEARLOG, true)) - VpnStatus.clearLog(); + Intent vpnIntent; + try { + vpnIntent = VpnService.prepare(this.getApplicationContext()); + } catch (NullPointerException npe) { + showAlertInMainActivity(R.string.vpn_error_establish); + finish(); + return; + } - // we got called to be the starting point, most likely a shortcut - mhideLog = intent.getBooleanExtra(EXTRA_HIDELOG, false); - VpnProfile profileToConnect = (VpnProfile) intent.getExtras().getSerializable(PROVIDER_PROFILE); + if (vpnIntent != null) { + // we don't have the permission yet to start the VPN - if (profileToConnect == null) { - VpnStatus.logError(R.string.shortcut_profile_notfound); - // show Log window to display error - showLogWindow(); - finish(); - } else { - mSelectedProfile = profileToConnect; - launchVPN(); + VpnStatus.updateStateString("USER_VPN_PERMISSION", "", R.string.state_user_vpn_permission, + ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT); + // Start the query + try { + startActivityForResult(vpnIntent, START_VPN_PROFILE); + } catch (ActivityNotFoundException ane) { + // Shame on you Sony! At least one user reported that + // an official Sony Xperia Arc S image triggers this exception + showAlertInMainActivity(R.string.no_vpn_support_image); } } } @@ -113,13 +108,8 @@ public class LaunchVPN extends Activity { protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - if(requestCode==START_VPN_PROFILE) { - SharedPreferences prefs = Preferences.getDefaultSharedPreferences(this); - boolean showLogWindow = prefs.getBoolean("showlogwindow", true); - - if(!mhideLog && showLogWindow) - showLogWindow(); - VPNLaunchHelper.startOpenVpn(mSelectedProfile, getBaseContext()); + if(requestCode==START_VPN_PROFILE && resultCode == Activity.RESULT_OK) { + EipCommand.launchVPNProfile(getApplicationContext(), selectedProfile, selectedGateway); finish(); } else if (resultCode == Activity.RESULT_CANCELED) { @@ -127,110 +117,29 @@ public class LaunchVPN extends Activity { 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(); - } - } - - void showLogWindow() { - - Intent startLW = new Intent(getBaseContext(), MainActivity.class); - startLW.putExtra(MainActivity.ACTION_SHOW_LOG_FRAGMENT, true); - startLW.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); - startActivity(startLW); - - } - - void showConfigErrorDialog(int vpnok) { - AlertDialog.Builder d = new AlertDialog.Builder(this); - d.setTitle(R.string.config_error_found); - d.setMessage(vpnok); - d.setPositiveButton(android.R.string.ok, new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - finish(); - - } - }); - d.setOnCancelListener(new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - finish(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + showAlertInMainActivity(R.string.nought_alwayson_warning); } - }); - 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) { - showConfigErrorDialog(vpnok); - return; - } - - Intent intent = null; - try { - intent = VpnService.prepare(this.getApplicationContext()); - } catch (NullPointerException npe) { - tellToReceiverOrBroadcast(this.getApplicationContext(), EIP_ACTION_PREPARE_VPN, RESULT_CANCELED); finish(); - return; - } - - // Check if we want to fix /dev/tun - SharedPreferences prefs = Preferences.getDefaultSharedPreferences(this); - boolean usecm9fix = prefs.getBoolean("useCM9Fix", false); - boolean loadTunModule = prefs.getBoolean("loadTunModule", false); - - if (loadTunModule) - execeuteSUcmd("insmod /system/lib/modules/tun.ko"); - - if (usecm9fix && !mCmfixed) { - execeuteSUcmd("chown system /dev/tun"); - } - - if (intent != null) { - VpnStatus.updateStateString("USER_VPN_PERMISSION", "", R.string.state_user_vpn_permission, - ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT); - // Start the query - try { - startActivityForResult(intent, START_VPN_PROFILE); - } catch (ActivityNotFoundException ane) { - // Shame on you Sony! At least one user reported that - // an official Sony Xperia Arc S image triggers this exception - VpnStatus.logError(R.string.no_vpn_support_image); - showLogWindow(); - } - } else { - onActivityResult(START_VPN_PROFILE, Activity.RESULT_OK, null); } + } + void showAlertInMainActivity(@StringRes int errorString) { + Bundle result = new Bundle(); + setErrorResult(result, errorString); + tellToReceiverOrBroadcast(this.getApplicationContext(), EIP_ACTION_PREPARE_VPN, RESULT_CANCELED, result); } - private void execeuteSUcmd(String command) { - try { - ProcessBuilder pb = new ProcessBuilder("su", "-c", command); - Process p = pb.start(); - int ret = p.waitFor(); - if (ret == 0) - mCmfixed = true; - } catch (InterruptedException | IOException e) { - VpnStatus.logException("SU command", e); - } + /** + * helper function to add error to result bundle + * + * @param result - result of an action + * @param errorMessageId - id of string resource describing the error + */ + void setErrorResult(Bundle result, @StringRes int errorMessageId) { + VpnStatus.logError(errorMessageId); + result.putString(ERRORS, getResources().getString(errorMessageId)); + result.putBoolean(BROADCAST_RESULT_KEY, false); } }
\ No newline at end of file diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java index 2c1a65dc..a734dd90 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java @@ -24,12 +24,13 @@ import android.os.IBinder; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.RemoteException; -import androidx.annotation.RequiresApi; import android.system.OsConstants; import android.text.TextUtils; import android.util.Log; import android.widget.Toast; +import androidx.annotation.RequiresApi; + import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.net.Inet6Address; @@ -45,9 +46,10 @@ import de.blinkt.openvpn.core.VpnStatus.StateListener; import de.blinkt.openvpn.core.connection.Connection; import de.blinkt.openvpn.core.connection.Obfs4Connection; import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.eip.EipStatus; import se.leap.bitmaskclient.eip.VpnNotificationManager; -import se.leap.bitmaskclient.pluggableTransports.Shapeshifter; import se.leap.bitmaskclient.firewall.FirewallManager; +import se.leap.bitmaskclient.pluggableTransports.Shapeshifter; import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_CONNECTED; import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT; @@ -61,7 +63,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac public static final String START_SERVICE_STICKY = "de.blinkt.openvpn.START_SERVICE_STICKY"; public static final String ALWAYS_SHOW_NOTIFICATION = "de.blinkt.openvpn.NOTIFICATION_ALWAYS_VISIBLE"; public static final String DISCONNECT_VPN = "de.blinkt.openvpn.DISCONNECT_VPN"; - public static final String NOTIFICATION_CHANNEL_BG_ID = "openvpn_bg"; public static final String NOTIFICATION_CHANNEL_NEWSTATUS_ID = "openvpn_newstat"; public static final String VPNSERVICE_TUN = "vpnservice-tun"; public final static String ORBOT_PACKAGE_NAME = "org.torproject.android"; @@ -92,11 +93,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac private Shapeshifter shapeshifter; private FirewallManager firewallManager; - 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 @@ -118,6 +114,11 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac public boolean isVpnRunning() throws RemoteException { return OpenVPNService.this.isVpnRunning(); } + + @Override + public void startWithForegroundNotification() throws RemoteException { + OpenVPNService.this.startWithForegroundNotification(); + } }; // From: http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java @@ -197,13 +198,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac firewallManager.stop(); } - private boolean runningOnAndroidTV() { - UiModeManager uiModeManager = (UiModeManager) getSystemService(UI_MODE_SERVICE); - if (uiModeManager == null) - return false; - return uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION; - } - synchronized void registerDeviceStateReceiver(OpenVPNManagement magnagement) { // Registers BroadcastReceiver to track network connection changes. IntentFilter filter = new IntentFilter(); @@ -218,8 +212,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac registerReceiver(mDeviceStateReceiver, filter); VpnStatus.addByteCountListener(mDeviceStateReceiver); - /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) - addLollipopCMListener(); */ } synchronized void unregisterDeviceStateReceiver() { @@ -235,9 +227,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } mDeviceStateReceiver = null; - /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) - removeLollipopCMListener();*/ - } @Override @@ -281,6 +270,13 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } @Override + public void startWithForegroundNotification() { + // Always show notification here to avoid problem with startForeground timeout + notificationManager.createOpenVpnNotificationChannel(); + notificationManager.buildForegroundServiceNotification(EipStatus.getInstance().getLevel(), this::onNotificationBuild); + } + + @Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent != null && intent.getBooleanExtra(ALWAYS_SHOW_NOTIFICATION, false)) @@ -334,6 +330,8 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } else { /* The intent is null when we are set as always-on or the service has been restarted. */ Log.d(TAG, "Starting VPN due to isAlwaysOn system settings or app crash."); + startWithForegroundNotification(); + mProfile = VpnStatus.getLastConnectedVpnProfile(this); VpnStatus.logInfo(R.string.service_restarted); @@ -375,9 +373,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } private void startOpenVPN() { - /** - * see change above (l. 292 ff) - */ //TODO: investigate how connections[n] with n>0 get called during vpn setup (on connection refused?) // Do we need to check if there's any obfs4 connection in mProfile.mConnections and start // the dispatcher here? Can we start the dispatcher at a later point of execution, e.g. when @@ -524,8 +519,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac @Override public void onCreate() { super.onCreate(); - notificationManager = new VpnNotificationManager(this, this); - notificationManager.createOpenVpnNotificationChannel(); + notificationManager = new VpnNotificationManager(this); firewallManager = new FirewallManager(this, true); } @@ -543,9 +537,8 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac // Just in case unregister for state VpnStatus.removeStateListener(this); VpnStatus.flushLog(); - notificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_BG_ID); - notificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_NEWSTATUS_ID); firewallManager.onDestroy(); + notificationManager.deleteOpenvpnNotificationChannel(); } private String getTunConfigString() { @@ -1013,14 +1006,10 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac if (mProcessThread == null && !mNotificationAlwaysVisible) return; - String channel = NOTIFICATION_CHANNEL_NEWSTATUS_ID; // Display byte count only after being connected - if (level == LEVEL_CONNECTED) { mDisplayBytecount = true; mConnecttime = System.currentTimeMillis(); - if (!runningOnAndroidTV()) - channel = NOTIFICATION_CHANNEL_BG_ID; firewallManager.start(); } else { mDisplayBytecount = false; @@ -1033,7 +1022,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac VpnStatus.getLastCleanLogMessage(this), level, 0, - channel); + NOTIFICATION_CHANNEL_NEWSTATUS_ID); } @@ -1064,7 +1053,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac null, LEVEL_CONNECTED, mConnecttime, - NOTIFICATION_CHANNEL_BG_ID); + NOTIFICATION_CHANNEL_NEWSTATUS_ID); } } @@ -1108,7 +1097,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac getString(resid), LEVEL_WAITING_FOR_USER_INPUT, 0, - NOTIFICATION_CHANNEL_BG_ID); + NOTIFICATION_CHANNEL_NEWSTATUS_ID); } @@ -1117,11 +1106,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac startForeground(notificationId, notification); } - @Override - public void onNotificationStop() { - stopForeground(true); - } - public void trigger_url_open(String info) { /* String channel = NOTIFICATION_CHANNEL_USERREQ_ID; 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 7c742746..540ca043 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java +++ b/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java @@ -7,7 +7,6 @@ package de.blinkt.openvpn.core; import android.annotation.TargetApi; import android.content.Context; -import android.content.Intent; import android.os.Build; import java.io.File; @@ -18,7 +17,6 @@ import java.util.Arrays; import java.util.Vector; import se.leap.bitmaskclient.R; -import de.blinkt.openvpn.VpnProfile; public class VPNLaunchHelper { private static final String MININONPIEVPN = "nopie_openvpn"; @@ -120,7 +118,6 @@ public class VPNLaunchHelper { return false; } - return true; } catch (IOException e) { VpnStatus.logException(e); @@ -129,20 +126,6 @@ public class VPNLaunchHelper { } - - 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/se/leap/bitmaskclient/base/MainActivity.java b/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java index 676e6c82..126c4a98 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java @@ -20,13 +20,14 @@ package se.leap.bitmaskclient.base; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; +import android.util.Log; + import androidx.annotation.StringRes; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentTransaction; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AppCompatActivity; -import android.util.Log; import org.json.JSONException; import org.json.JSONObject; @@ -35,24 +36,27 @@ import java.util.Observable; import java.util.Observer; import se.leap.bitmaskclient.R; -import se.leap.bitmaskclient.base.fragments.NavigationDrawerFragment; -import se.leap.bitmaskclient.eip.EIP; -import se.leap.bitmaskclient.eip.EipCommand; -import se.leap.bitmaskclient.eip.EipSetupListener; -import se.leap.bitmaskclient.eip.EipSetupObserver; import se.leap.bitmaskclient.base.fragments.EipFragment; import se.leap.bitmaskclient.base.fragments.ExcludeAppsFragment; import se.leap.bitmaskclient.base.fragments.LogFragment; +import se.leap.bitmaskclient.base.fragments.MainActivityErrorDialog; +import se.leap.bitmaskclient.base.fragments.NavigationDrawerFragment; import se.leap.bitmaskclient.base.models.Provider; import se.leap.bitmaskclient.base.models.ProviderObservable; -import se.leap.bitmaskclient.providersetup.models.LeapSRPSession; -import se.leap.bitmaskclient.providersetup.activities.LoginActivity; import se.leap.bitmaskclient.base.utils.PreferenceHelper; -import se.leap.bitmaskclient.base.fragments.MainActivityErrorDialog; +import se.leap.bitmaskclient.eip.EIP; +import se.leap.bitmaskclient.eip.EipCommand; +import se.leap.bitmaskclient.eip.EipSetupListener; +import se.leap.bitmaskclient.eip.EipSetupObserver; +import se.leap.bitmaskclient.providersetup.activities.LoginActivity; +import se.leap.bitmaskclient.providersetup.models.LeapSRPSession; +import static se.leap.bitmaskclient.R.string.downloading_vpn_certificate_failed; +import static se.leap.bitmaskclient.R.string.vpn_certificate_user_message; import static se.leap.bitmaskclient.base.models.Constants.ASK_TO_CANCEL_VPN; import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_CODE; import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY; +import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_LAUNCH_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_PREPARE_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START; import static se.leap.bitmaskclient.base.models.Constants.EIP_REQUEST; @@ -61,16 +65,14 @@ import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_CONFIGURE import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_LOG_IN; import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_SWITCH_PROVIDER; import static se.leap.bitmaskclient.base.models.Constants.SHARED_PREFERENCES; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.storeProviderInPreferences; +import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_INVALID_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_VPN_PREPARE; import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORID; import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORS; import static se.leap.bitmaskclient.providersetup.ProviderAPI.INCORRECTLY_DOWNLOADED_EIP_SERVICE; import static se.leap.bitmaskclient.providersetup.ProviderAPI.INCORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE; import static se.leap.bitmaskclient.providersetup.ProviderAPI.USER_MESSAGE; -import static se.leap.bitmaskclient.R.string.downloading_vpn_certificate_failed; -import static se.leap.bitmaskclient.R.string.vpn_certificate_user_message; -import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_INVALID_VPN_CERTIFICATE; -import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_VPN_PREPARE; -import static se.leap.bitmaskclient.base.utils.PreferenceHelper.storeProviderInPreferences; public class MainActivity extends AppCompatActivity implements EipSetupListener, Observer, ExcludeAppsFragment.ExcludedAppsCallback { @@ -256,7 +258,14 @@ public class MainActivity extends AppCompatActivity implements EipSetupListener, break; case EIP_ACTION_PREPARE_VPN: if (resultCode == RESULT_CANCELED) { - showMainActivityErrorDialog(getString(R.string.vpn_error_establish), ERROR_VPN_PREPARE); + String error = resultData.getString(ERRORS); + showMainActivityErrorDialog(error, ERROR_VPN_PREPARE); + } + break; + case EIP_ACTION_LAUNCH_VPN: + if (resultCode == RESULT_CANCELED) { + String error = resultData.getString(ERRORS); + showMainActivityErrorDialog(error); } break; } diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java index 4ff80ea6..b99182ae 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java @@ -52,6 +52,7 @@ import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; import butterknife.Unbinder; +import de.blinkt.openvpn.core.ConnectionStatus; import de.blinkt.openvpn.core.IOpenVPNServiceInternal; import de.blinkt.openvpn.core.OpenVPNService; import de.blinkt.openvpn.core.VpnStatus; @@ -319,10 +320,7 @@ public class EipFragment extends Fragment implements Observer { } else { EipCommand.startVPN(context.getApplicationContext(), false); } - vpnStateImage.showProgress(); - routedText.setVisibility(GONE); - vpnRoute.setVisibility(GONE); - colorBackgroundALittle(); + EipStatus.getInstance().updateState("UI_CONNECTING", "", 0, ConnectionStatus.LEVEL_START); } protected void stopEipIfPossible() { diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/MainActivityErrorDialog.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/MainActivityErrorDialog.java index 4b307f23..f036b411 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/MainActivityErrorDialog.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/MainActivityErrorDialog.java @@ -111,15 +111,15 @@ public class MainActivityErrorDialog extends DialogFragment { public Dialog onCreateDialog(Bundle savedInstanceState) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); Context applicationContext = getContext().getApplicationContext(); - builder.setMessage(reasonToFail) - .setNegativeButton(R.string.cancel, (dialog, id) -> { - }); + builder.setMessage(reasonToFail); switch (downloadError) { case ERROR_INVALID_VPN_CERTIFICATE: builder.setPositiveButton(R.string.update_certificate, (dialog, which) -> ProviderAPICommand.execute(getContext(), UPDATE_INVALID_VPN_CERTIFICATE, provider)); + builder.setNegativeButton(R.string.cancel, (dialog, id) -> {}); break; case NO_MORE_GATEWAYS: + builder.setNegativeButton(R.string.cancel, (dialog, id) -> {}); if (provider.supportsPluggableTransports()) { if (getUsePluggableTransports(applicationContext)) { builder.setPositiveButton(warning_option_try_ovpn, ((dialog, which) -> { @@ -139,9 +139,7 @@ public class MainActivityErrorDialog extends DialogFragment { } break; case ERROR_VPN_PREPARE: - builder.setPositiveButton(R.string.retry, (dialog, which) -> { - EipCommand.startVPN(applicationContext, false); - }); + builder.setPositiveButton(android.R.string.ok, (dialog, which) -> { }); break; default: break; diff --git a/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java b/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java index f213b7f9..a0d295bd 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java @@ -80,7 +80,7 @@ public interface Constants { String EIP_ACTION_START_BLOCKING_VPN = "se.leap.bitmaskclient.EIP_ACTION_START_BLOCKING_VPN"; String EIP_ACTION_STOP_BLOCKING_VPN = "se.leap.bitmaskclient.EIP_ACTION_STOP_BLOCKING_VPN"; String EIP_ACTION_PREPARE_VPN = "se.leap.bitmaskclient.EIP_ACTION_PREPARE_VPN"; - String EIP_ACTION_CONFIGURE_TETHERING = "se.leap.bitmaskclient.EIP_ACTION_CONFIGURE_TETHERING"; + String EIP_ACTION_LAUNCH_VPN = "se.leap.bitmaskclient.EIP_ACTION_LAUNCH_VPN"; String EIP_RECEIVER = "EIP.RECEIVER"; String EIP_REQUEST = "EIP.REQUEST"; @@ -104,6 +104,13 @@ public interface Constants { String PROVIDER_PROFILE_UUID = "Constants.PROVIDER_PROFILE_UUID"; String PROVIDER_PROFILE = "Constants.PROVIDER_PROFILE"; + //////////////////////////////////////////////// + // PRESHIPPED PROVIDER CONFIG + //////////////////////////////////////////////// + String URLS = "urls"; + String EXT_JSON = ".json"; + String EXT_PEM = ".pem"; + ////////////////////////////////////////////// // CREDENTIAL CONSTANTS ///////////////////////////////////////////// diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/InputStreamHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/InputStreamHelper.java index 77189dff..8a526499 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/utils/InputStreamHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/InputStreamHelper.java @@ -1,7 +1,11 @@ package se.leap.bitmaskclient.base.utils; +import org.json.JSONException; +import org.json.JSONObject; + import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.io.IOException; import java.io.InputStream; /** @@ -18,4 +22,26 @@ public class InputStreamHelper { java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A"); return s.hasNext() ? s.next() : ""; } + + public static String extractKeyFromInputStream(InputStream inputStream, String key) { + String value = ""; + + JSONObject fileContents = inputStreamToJson(inputStream); + if (fileContents != null) + value = fileContents.optString(key); + return value; + } + + public static JSONObject inputStreamToJson(InputStream inputStream) { + JSONObject json = null; + try { + byte[] bytes = new byte[inputStream.available()]; + if (inputStream.read(bytes) > 0) + json = new JSONObject(new String(bytes)); + inputStream.reset(); + } catch (IOException | JSONException e) { + e.printStackTrace(); + } + return json; + } } 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 e5cf70be..604a80fd 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java @@ -22,16 +22,19 @@ import android.content.Context; 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.os.ResultReceiver; +import android.util.Log; + import androidx.annotation.NonNull; import androidx.annotation.StringRes; import androidx.annotation.WorkerThread; import androidx.core.app.JobIntentService; import androidx.localbroadcastmanager.content.LocalBroadcastManager; -import android.util.Log; import org.json.JSONException; import org.json.JSONObject; @@ -43,29 +46,35 @@ import java.util.Observer; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; +import de.blinkt.openvpn.LaunchVPN; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.ConnectionStatus; import de.blinkt.openvpn.core.IOpenVPNServiceInternal; import de.blinkt.openvpn.core.OpenVPNService; +import de.blinkt.openvpn.core.Preferences; import de.blinkt.openvpn.core.VpnStatus; import de.blinkt.openvpn.core.connection.Connection; +import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.base.OnBootReceiver; import se.leap.bitmaskclient.base.models.ProviderObservable; -import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.base.utils.PreferenceHelper; import static android.app.Activity.RESULT_CANCELED; import static android.app.Activity.RESULT_OK; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN; +import static se.leap.bitmaskclient.R.string.vpn_certificate_is_invalid; +import static se.leap.bitmaskclient.R.string.warning_client_parsing_error_gateways; import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_GATEWAY_SETUP_OBSERVER_EVENT; +import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_CODE; import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY; +import static se.leap.bitmaskclient.base.models.Constants.CLEARLOG; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_CHECK_CERT_VALIDITY; -import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_CONFIGURE_TETHERING; -import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_IS_RUNNING; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START_ALWAYS_ON_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START_BLOCKING_VPN; +import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_IS_RUNNING; +import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_LAUNCH_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_STOP; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_STOP_BLOCKING_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_EARLY_ROUTES; @@ -75,13 +84,13 @@ import static se.leap.bitmaskclient.base.models.Constants.EIP_RESTART_ON_BOOT; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PROFILE; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_VPN_CERTIFICATE; import static se.leap.bitmaskclient.base.models.Constants.SHARED_PREFERENCES; -import static se.leap.bitmaskclient.R.string.vpn_certificate_is_invalid; -import static se.leap.bitmaskclient.R.string.warning_client_parsing_error_gateways; +import static se.leap.bitmaskclient.base.utils.ConfigHelper.ensureNotOnMainThread; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUsePluggableTransports; +import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_INVALID_PROFILE; import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_INVALID_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_VPN_PREPARE; import static se.leap.bitmaskclient.eip.EIP.EIPErrors.NO_MORE_GATEWAYS; import static se.leap.bitmaskclient.eip.EipResultBroadcast.tellToReceiverOrBroadcast; -import static se.leap.bitmaskclient.base.utils.ConfigHelper.ensureNotOnMainThread; -import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUsePluggableTransports; /** * EIP is the abstract base class for interacting with and managing the Encrypted @@ -106,6 +115,7 @@ public final class EIP extends JobIntentService implements Observer { // Service connection to OpenVpnService, shared between threads private volatile OpenVpnServiceConnection openVpnServiceConnection; private WeakReference<ResultReceiver> mResultRef = new WeakReference<>(null); + private volatile VoidVpnServiceConnection voidVpnServiceConnection; /** * Unique job ID for this service. @@ -116,7 +126,8 @@ public final class EIP extends JobIntentService implements Observer { UNKNOWN, ERROR_INVALID_VPN_CERTIFICATE, NO_MORE_GATEWAYS, - ERROR_VPN_PREPARE + ERROR_VPN_PREPARE, + ERROR_INVALID_PROFILE } /** @@ -146,6 +157,10 @@ public final class EIP extends JobIntentService implements Observer { openVpnServiceConnection.close(); openVpnServiceConnection = null; } + if (voidVpnServiceConnection != null) { + voidVpnServiceConnection.close(); + voidVpnServiceConnection = null; + } } /** @@ -175,7 +190,7 @@ public final class EIP extends JobIntentService implements Observer { int nClosestGateway; switch (action) { case EIP_ACTION_START: - boolean earlyRoutes = intent.getBooleanExtra(EIP_EARLY_ROUTES, true); + boolean earlyRoutes = intent.getBooleanExtra(EIP_EARLY_ROUTES, false); nClosestGateway = intent.getIntExtra(EIP_N_CLOSEST_GATEWAY, 0); startEIP(earlyRoutes, nClosestGateway); break; @@ -195,8 +210,9 @@ public final class EIP extends JobIntentService implements Observer { disconnect(); earlyRoutes(); break; - case EIP_ACTION_CONFIGURE_TETHERING: - Log.d(TAG, "TODO: implement tethering configuration"); + case EIP_ACTION_LAUNCH_VPN: + VpnProfile profile = (VpnProfile) intent.getSerializableExtra(PROVIDER_PROFILE); + launchProfile(profile); break; } } @@ -211,11 +227,11 @@ public final class EIP extends JobIntentService implements Observer { @SuppressLint("ApplySharedPref") private void startEIP(boolean earlyRoutes, int nClosestGateway) { Log.d(TAG, "start EIP with early routes: " + earlyRoutes + " and nClosest Gateway: " + nClosestGateway); + Bundle result = new Bundle(); if (!eipStatus.isBlockingVpnEstablished() && earlyRoutes) { - earlyRoutes(); + earlyRoutes(result); } - Bundle result = new Bundle(); if (!preferences.getBoolean(EIP_RESTART_ON_BOOT, false)) { preferences.edit().putBoolean(EIP_RESTART_ON_BOOT, true).commit(); } @@ -234,12 +250,11 @@ public final class EIP extends JobIntentService implements Observer { } Gateway gateway = gatewaysManager.select(nClosestGateway); - - if (launchActiveGateway(gateway, nClosestGateway)) { - tellToReceiverOrBroadcast(this, EIP_ACTION_START, RESULT_OK); - } else { - setErrorResult(result, NO_MORE_GATEWAYS.toString(), getStringResourceForNoMoreGateways(), getString(R.string.app_name)); + launchActiveGateway(gateway, nClosestGateway, result); + if (result.containsKey(BROADCAST_RESULT_KEY) && !result.getBoolean(BROADCAST_RESULT_KEY)) { tellToReceiverOrBroadcast(this, EIP_ACTION_START, RESULT_CANCELED, result); + } else { + tellToReceiverOrBroadcast(this, EIP_ACTION_START, RESULT_OK); } } @@ -250,9 +265,19 @@ public final class EIP extends JobIntentService implements Observer { private void startEIPAlwaysOnVpn() { GatewaysManager gatewaysManager = new GatewaysManager(getApplicationContext()); Gateway gateway = gatewaysManager.select(0); + Bundle result = new Bundle(); - if (!launchActiveGateway(gateway, 0)) { - Log.d(TAG, "startEIPAlwaysOnVpn no active profile available!"); + launchActiveGateway(gateway, 0, result); + if (result.containsKey(BROADCAST_RESULT_KEY) && !result.getBoolean(BROADCAST_RESULT_KEY)){ + VpnStatus.logWarning("ALWAYS-ON VPN: " + getString(R.string.no_vpn_profiles_defined)); + } + } + + private void earlyRoutes() { + Bundle result = new Bundle(); + earlyRoutes(result); + if (result.containsKey(BROADCAST_RESULT_KEY) && !result.getBoolean(BROADCAST_RESULT_KEY)){ + tellToReceiverOrBroadcast(this, EIP_ACTION_START_BLOCKING_VPN, RESULT_CANCELED, result); } } @@ -260,10 +285,27 @@ public final class EIP extends JobIntentService implements Observer { * Early routes are routes that block traffic until a new * VpnService is started properly. */ - private void earlyRoutes() { - Intent voidVpnLauncher = new Intent(getApplicationContext(), VoidVpnLauncher.class); - voidVpnLauncher.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(voidVpnLauncher); + private void earlyRoutes(Bundle result) { + Intent blockingIntent = VpnService.prepare(getApplicationContext()); // stops the VPN connection created by another application. + if (blockingIntent == null) { + try { + initVoidVpnServiceConnection(); + Intent voidVpnService = new Intent(getApplicationContext(), VoidVpnService.class); + voidVpnService.setAction(EIP_ACTION_START_BLOCKING_VPN); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + getApplicationContext().startForegroundService(voidVpnService); + voidVpnServiceConnection.getService().startWithForegroundNotification(); + } else { + getApplicationContext().startService(voidVpnService); + } + } catch (InterruptedException | IllegalStateException e) { + setErrorResult(result, R.string.vpn_error_establish, null); + } + } else { + Intent voidVpnLauncher = new Intent(getApplicationContext(), VoidVpnLauncher.class); + voidVpnLauncher.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(voidVpnLauncher); + } } /** @@ -271,21 +313,59 @@ public final class EIP extends JobIntentService implements Observer { * * @param gateway to connect to */ - private boolean launchActiveGateway(Gateway gateway, int nClosestGateway) { + private void launchActiveGateway(Gateway gateway, int nClosestGateway, Bundle result) { VpnProfile profile; Connection.TransportType transportType = getUsePluggableTransports(this) ? OBFS4 : OPENVPN; if (gateway == null || (profile = gateway.getProfile(transportType)) == null) { - return false; + setErrorResult(result, NO_MORE_GATEWAYS.toString(), getStringResourceForNoMoreGateways(), getString(R.string.app_name)); + return; } - Intent intent = new Intent(BROADCAST_GATEWAY_SETUP_OBSERVER_EVENT); - intent.putExtra(PROVIDER_PROFILE, profile); - intent.putExtra(Gateway.KEY_N_CLOSEST_GATEWAY, nClosestGateway); - LocalBroadcastManager.getInstance(this).sendBroadcast(intent); - return true; + Intent intent; + try { + intent = VpnService.prepare(getApplicationContext()); + } catch (NullPointerException npe) { + setErrorResult(result, ERROR_VPN_PREPARE.toString(), R.string.vpn_error_establish); + return; + } + if (intent == null) { + // vpn has been successfully prepared + + //inform EipSetupObserver about vpn connecting attempt + Intent setupObserverIntent = new Intent(BROADCAST_GATEWAY_SETUP_OBSERVER_EVENT); + setupObserverIntent.putExtra(PROVIDER_PROFILE, profile); + setupObserverIntent.putExtra(EIP_N_CLOSEST_GATEWAY, nClosestGateway); + LocalBroadcastManager.getInstance(this).sendBroadcast(setupObserverIntent); + + // Check if we need to clear the log + if (Preferences.getDefaultSharedPreferences(this).getBoolean(CLEARLOG, true)) + VpnStatus.clearLog(); + + // check profile configuration + int vpnok = profile.checkProfile(this); + if (vpnok != R.string.no_error_found) { + VpnStatus.logError(R.string.config_error_found); + VpnStatus.logError(vpnok); + setErrorResult(result, ERROR_INVALID_PROFILE.toString(), R.string.config_error_found); + return; + } + + //launch profile + launchProfile(profile, result); + + } else { + // vpn permission is missing + Intent permissionIntent = new Intent(getApplicationContext(), LaunchVPN.class); + permissionIntent.setAction(Intent.ACTION_MAIN); + permissionIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + permissionIntent.putExtra(PROVIDER_PROFILE, profile); + permissionIntent.putExtra(EIP_N_CLOSEST_GATEWAY, nClosestGateway); + startActivity(permissionIntent); + } } + /** * Stop VPN * First checks if the OpenVpnConnection is open then @@ -352,11 +432,14 @@ public final class EIP extends JobIntentService implements Observer { void setErrorResult(Bundle result, String errorId, @StringRes int errorMessageId, Object... args) { JSONObject errorJson = new JSONObject(); try { + String errorMessage; if (args != null) { - errorJson.put(ERRORS, getResources().getString(errorMessageId, args)); + errorMessage = getResources().getString(errorMessageId, args); } else { - errorJson.put(ERRORS, getResources().getString(errorMessageId)); + errorMessage = getResources().getString(errorMessageId); } + VpnStatus.logWarning("[EIP] error: " + errorMessage); + errorJson.put(ERRORS, errorMessage); errorJson.put(ERRORID, errorId); } catch (JSONException e) { e.printStackTrace(); @@ -406,6 +489,41 @@ public final class EIP extends JobIntentService implements Observer { return false; } + /** + * binds OpenVPNService to this service, starts it as a foreground service with a profile + * @param vpnProfile OpenVPN profile used to create a VPN connection + * @param result Bundle containing information about possible errors + */ + private void launchProfile(VpnProfile vpnProfile, Bundle result) { + Intent startVPN = vpnProfile.prepareStartService(getApplicationContext()); + if (startVPN != null) { + try { + initOpenVpnServiceConnection(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + //noinspection NewApi + getApplicationContext().startForegroundService(startVPN); + openVpnServiceConnection.getService().startWithForegroundNotification(); + } else { + getApplicationContext().startService(startVPN); + } + } catch (InterruptedException | IllegalStateException | RemoteException e) { + setErrorResult(result, R.string.vpn_error_establish, null); + } + } else { + setErrorResult(result, R.string.vpn_error_establish, null); + } + } + + private void launchProfile(VpnProfile vpnProfile) { + Bundle bundle = new Bundle(); + launchProfile(vpnProfile, bundle); + if (bundle.containsKey(BROADCAST_RESULT_KEY) && !bundle.getBoolean(BROADCAST_RESULT_KEY)) { + tellToReceiverOrBroadcast(this.getApplicationContext(), EIP_ACTION_LAUNCH_VPN, RESULT_CANCELED, bundle); + } else { + tellToReceiverOrBroadcast(this.getApplicationContext(), EIP_ACTION_LAUNCH_VPN, RESULT_OK); + } + } + private @StringRes int getStringResourceForNoMoreGateways() { if (ProviderObservable.getInstance().getCurrentProvider().supportsPluggableTransports()) { @@ -434,6 +552,69 @@ public final class EIP extends JobIntentService implements Observer { } /** + * Assigns a new VoidVpnServiceConnection to EIP's member variable voidVpnServiceConnection. + * Only one thread at a time can create the service connection, that will be shared between threads + * + * @throws InterruptedException thrown if thread gets interrupted + * @throws IllegalStateException thrown if this method was not called from a background thread + */ + private void initVoidVpnServiceConnection() throws InterruptedException, IllegalStateException { + if (voidVpnServiceConnection == null) { + Log.d(TAG, "serviceConnection is still null"); + voidVpnServiceConnection = new VoidVpnServiceConnection(this); + } + } + + public static class VoidVpnServiceConnection implements Closeable { + private final Context context; + private ServiceConnection serviceConnection; + private VoidVpnService voidVpnService; + + VoidVpnServiceConnection(Context context) throws InterruptedException, IllegalStateException { + this.context = context; + ensureNotOnMainThread(context); + Log.d(TAG, "initSynchronizedServiceConnection!"); + initSynchronizedServiceConnection(context); + } + + @Override + public void close() { + context.unbindService(serviceConnection); + } + + private void initSynchronizedServiceConnection(final Context context) throws InterruptedException { + final BlockingQueue<VoidVpnService> blockingQueue = new LinkedBlockingQueue<>(1); + this.serviceConnection = new ServiceConnection() { + volatile boolean mConnectedAtLeastOnce = false; + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + if (!mConnectedAtLeastOnce) { + mConnectedAtLeastOnce = true; + try { + VoidVpnService.VoidVpnServiceBinder binder = (VoidVpnService.VoidVpnServiceBinder) service; + blockingQueue.put(binder.getService()); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + } + }; + Intent intent = new Intent(context, VoidVpnService.class); + context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE); + voidVpnService = blockingQueue.take(); + } + + public VoidVpnService getService() { + return voidVpnService; + } + } + + /** * Creates a service connection to OpenVpnService. * The constructor blocks until the service is bound to the given Context. * Pattern stolen from android.security.KeyChain.java diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EipCommand.java b/app/src/main/java/se/leap/bitmaskclient/eip/EipCommand.java index 0650e8cd..46704419 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EipCommand.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipCommand.java @@ -9,14 +9,17 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import de.blinkt.openvpn.VpnProfile; + import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_CHECK_CERT_VALIDITY; -import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_CONFIGURE_TETHERING; +import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_LAUNCH_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START_BLOCKING_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_STOP; import static se.leap.bitmaskclient.base.models.Constants.EIP_EARLY_ROUTES; import static se.leap.bitmaskclient.base.models.Constants.EIP_N_CLOSEST_GATEWAY; import static se.leap.bitmaskclient.base.models.Constants.EIP_RECEIVER; +import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PROFILE; /** * Use this class to send commands to EIP @@ -73,6 +76,15 @@ public class EipCommand { execute(context, EIP_ACTION_STOP); } + public static void launchVPNProfile(@NonNull Context context, VpnProfile vpnProfile, Integer closestGateway) { + Intent baseIntent = new Intent(); + baseIntent.putExtra(PROVIDER_PROFILE, vpnProfile); + baseIntent.putExtra(EIP_N_CLOSEST_GATEWAY, closestGateway); + execute(context, EIP_ACTION_LAUNCH_VPN, null, baseIntent); + } + + public static void launchVoidVPN(@NonNull Context context) { execute(context, EIP_ACTION_START_BLOCKING_VPN);} + @VisibleForTesting public static void stopVPN(@NonNull Context context, ResultReceiver resultReceiver) { execute(context, EIP_ACTION_STOP, resultReceiver, null); @@ -87,13 +99,4 @@ public class EipCommand { execute(context, EIP_ACTION_CHECK_CERT_VALIDITY, resultReceiver, null); } - public static void configureTethering(@NonNull Context context) { - execute(context, EIP_ACTION_CONFIGURE_TETHERING); - } - - @VisibleForTesting - public static void configureTethering(@NonNull Context context, ResultReceiver resultReceiver) { - execute(context, EIP_ACTION_CONFIGURE_TETHERING); - } - } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java b/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java index f35e5e30..4706d550 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java @@ -54,10 +54,12 @@ import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_GATEWAY_SETU import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_PROVIDER_API_EVENT; import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_CODE; import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY; +import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_LAUNCH_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_PREPARE_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START_ALWAYS_ON_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_EARLY_ROUTES; +import static se.leap.bitmaskclient.base.models.Constants.EIP_N_CLOSEST_GATEWAY; import static se.leap.bitmaskclient.base.models.Constants.EIP_REQUEST; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PROFILE; @@ -158,14 +160,14 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta ProviderObservable.getInstance().updateProvider(provider); PreferenceHelper.storeProviderInPreferences(preferences, provider); if (EipStatus.getInstance().isDisconnected()) { - EipCommand.startVPN(context.getApplicationContext(), true); + EipCommand.startVPN(context.getApplicationContext(), false); } break; case CORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE: provider = resultData.getParcelable(PROVIDER_KEY); ProviderObservable.getInstance().updateProvider(provider); PreferenceHelper.storeProviderInPreferences(preferences, provider); - EipCommand.startVPN(context.getApplicationContext(), true); + EipCommand.startVPN(context.getApplicationContext(), false); break; case CORRECTLY_DOWNLOADED_GEOIP_JSON: provider = resultData.getParcelable(PROVIDER_KEY); @@ -230,6 +232,13 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta EipStatus.refresh(); } break; + case EIP_ACTION_LAUNCH_VPN: + if (resultCode == RESULT_CANCELED) { + VpnStatus.logError("Error starting VpnService."); + finishGatewaySetup(false); + EipStatus.refresh(); + } + break; default: break; } @@ -252,20 +261,19 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta return; } setupVpnProfile = vpnProfile; - setupNClosestGateway.set(event.getIntExtra(Gateway.KEY_N_CLOSEST_GATEWAY, 0)); + setupNClosestGateway.set(event.getIntExtra(EIP_N_CLOSEST_GATEWAY, 0)); Log.d(TAG, "bitmaskapp add state listener"); VpnStatus.addStateListener(this); - - launchVPN(setupVpnProfile); } private void launchVPN(VpnProfile vpnProfile) { + EipCommand.launchVPNProfile(context, vpnProfile, setupNClosestGateway.get()); Intent intent = new Intent(context.getApplicationContext(), LaunchVPN.class); intent.setAction(Intent.ACTION_MAIN); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra(LaunchVPN.EXTRA_HIDELOG, true); intent.putExtra(PROVIDER_PROFILE, vpnProfile); - intent.putExtra(Gateway.KEY_N_CLOSEST_GATEWAY, setupNClosestGateway.get()); + intent.putExtra(EIP_N_CLOSEST_GATEWAY, setupNClosestGateway.get()); context.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 ad84ec5a..bc123683 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java @@ -77,7 +77,7 @@ public class EipStatus extends Observable implements VpnStatus.StateListener { currentStatus.setLocalizedResId(localizedResId); currentStatus.setLevel(level); currentStatus.setEipLevel(level); - if (tmp != currentStatus.getLevel() || "RECONNECTING".equals(state)) { + if (tmp != currentStatus.getLevel() || "RECONNECTING".equals(state) || "UI_CONNECTING".equals(state)) { refresh(); } } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java index 1df54e6e..6b44856e 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java @@ -55,7 +55,6 @@ import static se.leap.bitmaskclient.base.models.Constants.VERSION; public class Gateway { public final static String TAG = Gateway.class.getSimpleName(); - public final static String KEY_N_CLOSEST_GATEWAY = "N_CLOSEST_GATEWAY"; private JSONObject generalConfiguration; private JSONObject secrets; diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnLauncher.java b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnLauncher.java index e6905448..e2cd86b9 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnLauncher.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnLauncher.java @@ -3,10 +3,10 @@ package se.leap.bitmaskclient.eip; import android.app.Activity; import android.content.Intent; import android.net.VpnService; -import android.os.Build; import android.os.Bundle; -import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START_BLOCKING_VPN; +import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_PREPARE_VPN; +import static se.leap.bitmaskclient.eip.EipResultBroadcast.tellToReceiverOrBroadcast; public class VoidVpnLauncher extends Activity { @@ -19,24 +19,25 @@ public class VoidVpnLauncher extends Activity { } public void setUp() { - Intent blocking_intent = VpnService.prepare(getApplicationContext()); // stops the VPN connection created by another application. - if (blocking_intent != null) - startActivityForResult(blocking_intent, VPN_USER_PERMISSION); + Intent blockingIntent = null; + try { + blockingIntent = VpnService.prepare(getApplicationContext()); // stops the VPN connection created by another application. + } catch (NullPointerException npe) { + tellToReceiverOrBroadcast(this.getApplicationContext(), EIP_ACTION_PREPARE_VPN, RESULT_CANCELED); + finish(); + } + if (blockingIntent != null) { + startActivityForResult(blockingIntent, VPN_USER_PERMISSION); + } else { - onActivityResult(VPN_USER_PERMISSION, RESULT_OK, null); + EipCommand.startBlockingVPN(getApplicationContext()); } } protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == VPN_USER_PERMISSION) { if (resultCode == RESULT_OK) { - Intent void_vpn_service = new Intent(getApplicationContext(), VoidVpnService.class); - void_vpn_service.setAction(EIP_ACTION_START_BLOCKING_VPN); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - startForegroundService(void_vpn_service); - } else { - startService(void_vpn_service); - } + EipCommand.launchVoidVPN(getApplicationContext()); } } finish(); diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java index 77038492..68ad78e4 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java @@ -21,7 +21,9 @@ import android.app.Notification; import android.content.Intent; import android.content.SharedPreferences; import android.net.VpnService; +import android.os.Binder; import android.os.Build; +import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.system.OsConstants; import android.util.Log; @@ -53,12 +55,27 @@ public class VoidVpnService extends VpnService implements Observer, VpnNotificat private EipStatus eipStatus; private VpnNotificationManager notificationManager; + private final IBinder binder = new VoidVpnServiceBinder(); + public class VoidVpnServiceBinder extends Binder { + VoidVpnService getService() { + // Return this instance of LocalService so clients can call public methods + return VoidVpnService.this; + } + } + + @Override + public IBinder onBind(Intent intent) { + return binder; + } + + + @Override public void onCreate() { super.onCreate(); eipStatus = EipStatus.getInstance(); eipStatus.addObserver(this); - notificationManager = new VpnNotificationManager(this, this); + notificationManager = new VpnNotificationManager(this); notificationManager.createVoidVpnNotificationChannel(); } @@ -99,14 +116,19 @@ public class VoidVpnService extends VpnService implements Observer, VpnNotificat closeFd(); } + @Override + public void onDestroy() { + super.onDestroy(); + notificationManager.deleteVoidVpnNotificationChannel(); + } + private void stop() { - notificationManager.stopNotifications(NOTIFICATION_CHANNEL_NEWSTATUS_ID); - notificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_NEWSTATUS_ID); if (thread != null) { thread.interrupt(); } closeFd(); VpnStatus.updateStateString("NOPROCESS", "BLOCKING VPN STOPPED", R.string.state_noprocess, ConnectionStatus.LEVEL_NOTCONNECTED); + stopForeground(true); } public static boolean isRunning() throws NullPointerException { @@ -185,9 +207,11 @@ public class VoidVpnService extends VpnService implements Observer, VpnNotificat notificationManager.buildVoidVpnNotification( blockingMessage, blockingMessage, - eipStatus.getLevel()); + eipStatus.getLevel(), + this + ); } else { - notificationManager.stopNotifications(NOTIFICATION_CHANNEL_NEWSTATUS_ID); + stopForeground(true); } } @@ -196,9 +220,8 @@ public class VoidVpnService extends VpnService implements Observer, VpnNotificat startForeground(notificationId, notification); } - @Override - public void onNotificationStop() { - stopForeground(true); + public void startWithForegroundNotification() { + } } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/VpnNotificationManager.java b/app/src/main/java/se/leap/bitmaskclient/eip/VpnNotificationManager.java index b3ed5394..b007715b 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnNotificationManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnNotificationManager.java @@ -27,32 +27,34 @@ import android.graphics.Color; import android.graphics.Typeface; import android.os.Build; import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.core.app.NotificationCompat; -import androidx.core.app.NotificationManagerCompat; import android.text.Spannable; import android.text.SpannableString; import android.text.TextUtils; import android.text.style.StyleSpan; import android.widget.RemoteViews; +import androidx.annotation.NonNull; +import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; + import de.blinkt.openvpn.LaunchVPN; import de.blinkt.openvpn.core.ConnectionStatus; import de.blinkt.openvpn.core.OpenVPNService; -import se.leap.bitmaskclient.base.MainActivity; +import de.blinkt.openvpn.core.VpnStatus; import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.base.MainActivity; import se.leap.bitmaskclient.base.StartActivity; import static android.os.Build.VERSION_CODES.O; -import static androidx.core.app.NotificationCompat.PRIORITY_HIGH; -import static androidx.core.app.NotificationCompat.PRIORITY_MAX; -import static androidx.core.app.NotificationCompat.PRIORITY_MIN; import static android.text.TextUtils.isEmpty; +import static androidx.core.app.NotificationCompat.PRIORITY_DEFAULT; +import static androidx.core.app.NotificationCompat.PRIORITY_MAX; import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_NONETWORK; import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT; +import static se.leap.bitmaskclient.base.MainActivity.ACTION_SHOW_VPN_FRAGMENT; import static se.leap.bitmaskclient.base.models.Constants.ASK_TO_CANCEL_VPN; +import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_STOP_BLOCKING_VPN; -import static se.leap.bitmaskclient.base.MainActivity.ACTION_SHOW_VPN_FRAGMENT; /** * Created by cyberta on 14.01.18. @@ -60,29 +62,20 @@ import static se.leap.bitmaskclient.base.MainActivity.ACTION_SHOW_VPN_FRAGMENT; public class VpnNotificationManager { + private static final String TAG = VpnNotificationManager.class.getSimpleName(); Context context; - private VpnServiceCallback vpnServiceCallback; - private NotificationManager notificationManager; - private NotificationManagerCompat compatNotificationManager; - private String[] notificationChannels = { - OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID, - OpenVPNService.NOTIFICATION_CHANNEL_BG_ID, - VoidVpnService.NOTIFICATION_CHANNEL_NEWSTATUS_ID}; - private String lastNotificationChannel = ""; + private final NotificationManagerCompat compatNotificationManager; public interface VpnServiceCallback { void onNotificationBuild(int notificationId, Notification notification); - void onNotificationStop(); } - public VpnNotificationManager(@NonNull Context context, @NonNull VpnServiceCallback vpnServiceCallback) { + public VpnNotificationManager(@NonNull Context context) { this.context = context; - notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); compatNotificationManager = NotificationManagerCompat.from(context); - this.vpnServiceCallback = vpnServiceCallback; } - public void buildVoidVpnNotification(final String msg, String tickerText, ConnectionStatus status) { + public void buildVoidVpnNotification(final String msg, String tickerText, ConnectionStatus status, VpnServiceCallback callback) { //TODO: implement extra Dashboard.ACTION_ASK_TO_CANCEL_BLOCKING_VPN NotificationCompat.Action.Builder actionBuilder = new NotificationCompat.Action.Builder(R.drawable.ic_menu_close_clear_cancel, context.getString(R.string.vpn_button_turn_off_blocking), getStopVoidVpnIntent()); @@ -97,28 +90,45 @@ public class VpnNotificationManager { PRIORITY_MAX, 0, getMainActivityIntent(), - actionBuilder.build()); + actionBuilder.build(), + callback + ); } - public void stopNotifications(String notificationChannelNewstatusId) { - vpnServiceCallback.onNotificationStop(); - compatNotificationManager.cancel(notificationChannelNewstatusId.hashCode()); - } - public void deleteNotificationChannel(String notificationChannel) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && - notificationManager.getNotificationChannel(notificationChannel) != null) { - notificationManager.deleteNotificationChannel(notificationChannel); + public void buildForegroundServiceNotification(ConnectionStatus connectionStatus, + VpnServiceCallback callback) { + String message = ""; + // if the app was killed by the system getLastCleanLogMessage returns an empty string + // because the state doesn't get persisted. We can use LEVEL_NOTCONNECTED as an indicator for + // that case because the openvpn service won't be connected then + if (connectionStatus == ConnectionStatus.LEVEL_NOTCONNECTED) { + message = context.getString(R.string.eip_state_not_connected); + } else { + message = VpnStatus.getLastCleanLogMessage(context); } + + NotificationCompat.Action.Builder actionBuilder = new NotificationCompat.Action.Builder(0, + context.getString(R.string.vpn_button_turn_on), getStartOpenvpnIntent()); + + buildVpnNotification( + "", + message, + null, + "", + connectionStatus, + OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID, + PRIORITY_DEFAULT, + 0, + getMainActivityIntent(), + actionBuilder.build(), + callback + ); } - /** - * @param msg - * @param tickerText - * @param status - * @param when - */ - public void buildOpenVpnNotification(String profileName, boolean isObfuscated, String msg, String tickerText, ConnectionStatus status, long when, String notificationChannelNewstatusId) { + public void buildOpenVpnNotification(String profileName, boolean isObfuscated, String msg, + String tickerText, ConnectionStatus status, long when, + String notificationChannelNewstatusId, VpnServiceCallback vpnServiceCallback) { String cancelString; CharSequence bigmessage = null; String ghostIcon = new String(Character.toChars(0x1f309)); @@ -158,7 +168,7 @@ public class VpnNotificationManager { String appName = context.getString(R.string.app_name); if (isEmpty(profileName)) { title = appName; - } else { + } else { title = context.getString(R.string.notifcation_title_bitmask, appName, profileName); } @@ -167,15 +177,6 @@ public class VpnNotificationManager { contentIntent = getUserInputIntent(msg); else contentIntent = getMainActivityIntent(); - - int priority; - if (OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID.equals(notificationChannelNewstatusId)) { - priority = PRIORITY_HIGH; - } else { - // background channel - priority = PRIORITY_MIN; - } - buildVpnNotification( title, msg, @@ -183,10 +184,25 @@ public class VpnNotificationManager { tickerText, status, notificationChannelNewstatusId, - priority, + PRIORITY_DEFAULT, when, contentIntent, - actionBuilder.build()); + actionBuilder.build(), + vpnServiceCallback); + } + + public void buildOpenVpnNotification(String profileName, boolean isObfuscated, String msg, String tickerText, ConnectionStatus status, long when, String notificationChannelNewstatusId) { + buildOpenVpnNotification(profileName, isObfuscated, msg, tickerText, status, when, notificationChannelNewstatusId, null); + } + + public void deleteOpenvpnNotificationChannel() { + compatNotificationManager.cancelAll(); + compatNotificationManager.deleteNotificationChannel(OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID); + } + + public void deleteVoidVpnNotificationChannel() { + compatNotificationManager.cancelAll(); + compatNotificationManager.deleteNotificationChannel(VoidVpnService.NOTIFICATION_CHANNEL_NEWSTATUS_ID); } @@ -197,16 +213,19 @@ public class VpnNotificationManager { } // Connection status change messages - CharSequence name = context.getString(R.string.channel_name_status); - NotificationChannel mChannel = new NotificationChannel(VoidVpnService.NOTIFICATION_CHANNEL_NEWSTATUS_ID, - name, NotificationManager.IMPORTANCE_DEFAULT); - - mChannel.setDescription(context.getString(R.string.channel_description_status)); - mChannel.enableLights(true); - - mChannel.setLightColor(Color.BLUE); - mChannel.setSound(null, null); - notificationManager.createNotificationChannel(mChannel); + NotificationChannel channel = compatNotificationManager.getNotificationChannel(VoidVpnService.NOTIFICATION_CHANNEL_NEWSTATUS_ID); + if (channel == null) { + CharSequence name = context.getString(R.string.channel_name_status); + channel = new NotificationChannel(VoidVpnService.NOTIFICATION_CHANNEL_NEWSTATUS_ID, + name, NotificationManager.IMPORTANCE_DEFAULT); + + channel.setDescription(context.getString(R.string.channel_description_status)); + channel.enableLights(true); + + channel.setLightColor(Color.BLUE); + channel.setSound(null, null); + compatNotificationManager.createNotificationChannel(channel); + } } @TargetApi(O) @@ -215,29 +234,20 @@ public class VpnNotificationManager { return; } - // Background message - CharSequence name = context.getString(R.string.channel_name_background); - NotificationChannel mChannel = new NotificationChannel(OpenVPNService.NOTIFICATION_CHANNEL_BG_ID, - name, NotificationManager.IMPORTANCE_MIN); - - mChannel.setDescription(context.getString(R.string.channel_description_background)); - mChannel.enableLights(false); - - mChannel.setLightColor(Color.DKGRAY); - notificationManager.createNotificationChannel(mChannel); - // Connection status change messages - name = context.getString(R.string.channel_name_status); - mChannel = new NotificationChannel(OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID, - name, NotificationManager.IMPORTANCE_DEFAULT); - - - mChannel.setDescription(context.getString(R.string.channel_description_status)); - mChannel.enableLights(true); - - mChannel.setLightColor(Color.BLUE); - mChannel.setSound(null, null); - notificationManager.createNotificationChannel(mChannel); + NotificationChannel channel = compatNotificationManager.getNotificationChannel(OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID); + if (channel == null) { + CharSequence name = context.getString(R.string.channel_name_status); + channel = new NotificationChannel(OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID, + name, NotificationManager.IMPORTANCE_DEFAULT); + + channel.setDescription(context.getString(R.string.channel_description_status)); + channel.enableLights(true); + + channel.setLightColor(Color.BLUE); + channel.setSound(null, null); + compatNotificationManager.createNotificationChannel(channel); + } } /** @@ -253,7 +263,9 @@ public class VpnNotificationManager { return remoteViews; } - private void buildVpnNotification(String title, String message, CharSequence bigMessage, String tickerText, ConnectionStatus status, String notificationChannelNewstatusId, int priority, long when, PendingIntent contentIntent, NotificationCompat.Action notificationAction) { + private void buildVpnNotification(String title, String message, CharSequence bigMessage, String tickerText, + ConnectionStatus status, String notificationChannelNewstatusId, int priority, + long when, PendingIntent contentIntent, NotificationCompat.Action notificationAction, VpnServiceCallback vpnServiceCallback) { NotificationCompat.Builder nCompatBuilder = new NotificationCompat.Builder(context, notificationChannelNewstatusId); int icon = getIconByConnectionStatus(status); @@ -291,16 +303,10 @@ public class VpnNotificationManager { Notification notification = nCompatBuilder.build(); int notificationId = notificationChannelNewstatusId.hashCode(); - if (!notificationChannelNewstatusId.equals(lastNotificationChannel)) { - // Cancel old notification - for (String channel : notificationChannels) { - stopNotifications(channel); - } - } - compatNotificationManager.notify(notificationId, notification); - vpnServiceCallback.onNotificationBuild(notificationId, notification); - lastNotificationChannel = notificationChannelNewstatusId; + if (vpnServiceCallback != null) { + vpnServiceCallback.onNotificationBuild(notificationId, notification); + } } private PendingIntent getMainActivityIntent() { @@ -308,6 +314,12 @@ public class VpnNotificationManager { return PendingIntent.getActivity(context, 0, startActivity, PendingIntent.FLAG_CANCEL_CURRENT); } + private PendingIntent getStartOpenvpnIntent() { + Intent startIntent = new Intent(context, EIP.class); + startIntent.setAction(EIP_ACTION_START); + return PendingIntent.getService(context, 0, startIntent, PendingIntent.FLAG_CANCEL_CURRENT); + } + private PendingIntent getStopVoidVpnIntent() { Intent stopVoidVpnIntent = new Intent (context, VoidVpnService.class); stopVoidVpnIntent.setAction(EIP_ACTION_STOP_BLOCKING_VPN); diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiConnector.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiConnector.java index ba902566..c863abd4 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiConnector.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiConnector.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Locale; import java.util.Scanner; +import de.blinkt.openvpn.core.VpnStatus; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; @@ -68,6 +69,9 @@ public class ProviderApiConnector { Request request = requestBuilder.build(); Response response = okHttpClient.newCall(request).execute(); + if (!response.isSuccessful()) { + VpnStatus.logWarning("[API] API request failed canConnect(): " + url); + } return response.isSuccessful(); } @@ -88,6 +92,9 @@ public class ProviderApiConnector { Request request = requestBuilder.build(); Response response = okHttpClient.newCall(request).execute(); + if (!response.isSuccessful()) { + VpnStatus.logWarning("[API] API request failed: " + url); + } InputStream inputStream = response.body().byteStream(); Scanner scanner = new Scanner(inputStream).useDelimiter("\\A"); if (scanner.hasNext()) { diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java index 8a0c8f02..c5dc6572 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java @@ -52,17 +52,29 @@ import java.util.NoSuchElementException; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLPeerUnverifiedException; +import de.blinkt.openvpn.core.VpnStatus; import okhttp3.OkHttpClient; +import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.base.models.Constants.CREDENTIAL_ERRORS; import se.leap.bitmaskclient.base.models.Provider; import se.leap.bitmaskclient.base.models.ProviderObservable; -import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.base.utils.ConfigHelper; import se.leap.bitmaskclient.providersetup.connectivity.OkHttpClientGenerator; import se.leap.bitmaskclient.providersetup.models.LeapSRPSession; import se.leap.bitmaskclient.providersetup.models.SrpCredentials; import se.leap.bitmaskclient.providersetup.models.SrpRegistrationData; -import se.leap.bitmaskclient.base.utils.ConfigHelper; +import static se.leap.bitmaskclient.R.string.certificate_error; +import static se.leap.bitmaskclient.R.string.error_io_exception_user_message; +import static se.leap.bitmaskclient.R.string.error_json_exception_user_message; +import static se.leap.bitmaskclient.R.string.error_no_such_algorithm_exception_user_message; +import static se.leap.bitmaskclient.R.string.malformed_url; +import static se.leap.bitmaskclient.R.string.server_unreachable_message; +import static se.leap.bitmaskclient.R.string.service_is_down_error; +import static se.leap.bitmaskclient.R.string.vpn_certificate_is_invalid; +import static se.leap.bitmaskclient.R.string.warning_corrupted_provider_cert; +import static se.leap.bitmaskclient.R.string.warning_corrupted_provider_details; +import static se.leap.bitmaskclient.R.string.warning_expired_provider_cert; import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_PROVIDER_API_EVENT; import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_CODE; import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY; @@ -76,6 +88,11 @@ import static se.leap.bitmaskclient.base.models.Provider.CA_CERT; import static se.leap.bitmaskclient.base.models.Provider.GEOIP_URL; import static se.leap.bitmaskclient.base.models.Provider.PROVIDER_API_IP; import static se.leap.bitmaskclient.base.models.Provider.PROVIDER_IP; +import static se.leap.bitmaskclient.base.utils.ConfigHelper.getFingerprintFromCertificate; +import static se.leap.bitmaskclient.base.utils.ConfigHelper.getProviderFormattedString; +import static se.leap.bitmaskclient.base.utils.ConfigHelper.parseRsaKeyFromString; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.deleteProviderDetailsFromPreferences; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getFromPersistedProvider; import static se.leap.bitmaskclient.providersetup.ProviderAPI.BACKEND_ERROR_KEY; import static se.leap.bitmaskclient.providersetup.ProviderAPI.BACKEND_ERROR_MESSAGE; import static se.leap.bitmaskclient.providersetup.ProviderAPI.CORRECTLY_DOWNLOADED_EIP_SERVICE; @@ -111,22 +128,6 @@ import static se.leap.bitmaskclient.providersetup.ProviderAPI.USER_MESSAGE; import static se.leap.bitmaskclient.providersetup.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_CERTIFICATE_PINNING; import static se.leap.bitmaskclient.providersetup.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_CORRUPTED_PROVIDER_JSON; import static se.leap.bitmaskclient.providersetup.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_INVALID_CERTIFICATE; -import static se.leap.bitmaskclient.R.string.certificate_error; -import static se.leap.bitmaskclient.R.string.error_io_exception_user_message; -import static se.leap.bitmaskclient.R.string.error_json_exception_user_message; -import static se.leap.bitmaskclient.R.string.error_no_such_algorithm_exception_user_message; -import static se.leap.bitmaskclient.R.string.malformed_url; -import static se.leap.bitmaskclient.R.string.server_unreachable_message; -import static se.leap.bitmaskclient.R.string.service_is_down_error; -import static se.leap.bitmaskclient.R.string.vpn_certificate_is_invalid; -import static se.leap.bitmaskclient.R.string.warning_corrupted_provider_cert; -import static se.leap.bitmaskclient.R.string.warning_corrupted_provider_details; -import static se.leap.bitmaskclient.R.string.warning_expired_provider_cert; -import static se.leap.bitmaskclient.base.utils.ConfigHelper.getFingerprintFromCertificate; -import static se.leap.bitmaskclient.base.utils.ConfigHelper.getProviderFormattedString; -import static se.leap.bitmaskclient.base.utils.ConfigHelper.parseRsaKeyFromString; -import static se.leap.bitmaskclient.base.utils.PreferenceHelper.deleteProviderDetailsFromPreferences; -import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getFromPersistedProvider; /** * Implements the logic of the http api calls. The methods of this class needs to be called from @@ -494,6 +495,7 @@ public abstract class ProviderApiManagerBase { } else { broadcastEvent(resultCode, resultData); } + handleEventSummaryErrorLog(resultCode); } private void broadcastEvent(int resultCode , Bundle resultData) { @@ -504,6 +506,40 @@ public abstract class ProviderApiManagerBase { serviceCallback.broadcastEvent(intentUpdate); } + private void handleEventSummaryErrorLog(int resultCode) { + String event = null; + switch (resultCode) { + case FAILED_LOGIN: + event = "login."; + break; + case FAILED_SIGNUP: + event = "signup."; + break; + case SUCCESSFUL_LOGOUT: + event = "logout."; + break; + case INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE: + event = "download of vpn certificate."; + break; + case PROVIDER_NOK: + event = "setup or update provider details."; + break; + case INCORRECTLY_DOWNLOADED_EIP_SERVICE: + event = "update eip-service.json"; + break; + case INCORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE: + event = "update invalid vpn certificate."; + break; + case INCORRECTLY_DOWNLOADED_GEOIP_JSON: + event = "download menshen service json."; + break; + default: + break; + } + if (event != null) { + VpnStatus.logWarning("[API] failed provider API event: " + event); + } + } /** * Validates parameters entered by the user to log in @@ -598,6 +634,7 @@ public abstract class ProviderApiManagerBase { } catch (NullPointerException | JSONException e) { e.printStackTrace(); responseJson = getErrorMessageAsJson(error_json_exception_user_message); + VpnStatus.logWarning("[API] got null response for request: " + url); } return responseJson; @@ -612,21 +649,29 @@ public abstract class ProviderApiManagerBase { } catch (NullPointerException npe) { plainResponseBody = formatErrorMessage(error_json_exception_user_message); + VpnStatus.logWarning("[API] Null response body for request " + url + ": " + npe.getLocalizedMessage()); } catch (UnknownHostException | SocketTimeoutException e) { plainResponseBody = formatErrorMessage(server_unreachable_message); + VpnStatus.logWarning("[API] UnknownHostException or SocketTimeoutException for request " + url + ": " + e.getLocalizedMessage()); } catch (MalformedURLException e) { plainResponseBody = formatErrorMessage(malformed_url); + VpnStatus.logWarning("[API] MalformedURLException for request " + url + ": " + e.getLocalizedMessage()); } catch (SSLHandshakeException | SSLPeerUnverifiedException e) { plainResponseBody = formatErrorMessage(certificate_error); + VpnStatus.logWarning("[API] SSLHandshakeException or SSLPeerUnverifiedException for request " + url + ": " + e.getLocalizedMessage()); } catch (ConnectException e) { plainResponseBody = formatErrorMessage(service_is_down_error); + VpnStatus.logWarning("[API] ConnectException for request " + url + ": " + e.getLocalizedMessage()); } catch (IllegalArgumentException e) { plainResponseBody = formatErrorMessage(error_no_such_algorithm_exception_user_message); + VpnStatus.logWarning("[API] IllegalArgumentException for request " + url + ": " + e.getLocalizedMessage()); } catch (UnknownServiceException e) { //unable to find acceptable protocols - tlsv1.2 not enabled? plainResponseBody = formatErrorMessage(error_no_such_algorithm_exception_user_message); + VpnStatus.logWarning("[API] UnknownServiceException for request " + url + ": " + e.getLocalizedMessage()); } catch (IOException e) { plainResponseBody = formatErrorMessage(error_io_exception_user_message); + VpnStatus.logWarning("[API] IOException for request " + url + ": " + e.getLocalizedMessage()); } return plainResponseBody; @@ -647,19 +692,26 @@ public abstract class ProviderApiManagerBase { return ProviderApiConnector.canConnect(okHttpClient, providerUrl); } catch (UnknownHostException | SocketTimeoutException e) { + VpnStatus.logWarning("[API] UnknownHostException or SocketTimeoutException during connection check: " + e.getLocalizedMessage()); setErrorResult(result, server_unreachable_message, null); } catch (MalformedURLException e) { + VpnStatus.logWarning("[API] MalformedURLException during connection check: " + e.getLocalizedMessage()); setErrorResult(result, malformed_url, null); } catch (SSLHandshakeException e) { + VpnStatus.logWarning("[API] SSLHandshakeException during connection check: " + e.getLocalizedMessage()); setErrorResult(result, warning_corrupted_provider_cert, ERROR_INVALID_CERTIFICATE.toString()); } catch (ConnectException e) { + VpnStatus.logWarning("[API] ConnectException during connection check: " + e.getLocalizedMessage()); setErrorResult(result, service_is_down_error, null); } catch (IllegalArgumentException e) { + VpnStatus.logWarning("[API] IllegalArgumentException during connection check: " + e.getLocalizedMessage()); setErrorResult(result, error_no_such_algorithm_exception_user_message, null); } catch (UnknownServiceException e) { + VpnStatus.logWarning("[API] UnknownServiceException during connection check: " + e.getLocalizedMessage()); //unable to find acceptable protocols - tlsv1.2 not enabled? setErrorResult(result, error_no_such_algorithm_exception_user_message, null); } catch (IOException e) { + VpnStatus.logWarning("[API] IOException during connection check: " + e.getLocalizedMessage()); setErrorResult(result, error_io_exception_user_message, null); } return false; @@ -768,6 +820,7 @@ public abstract class ProviderApiManagerBase { String caCert = provider.getCaCert(); if (ConfigHelper.checkErroneousDownload(caCert)) { + VpnStatus.logWarning("[API] No provider cert."); return result; } @@ -802,6 +855,7 @@ public abstract class ProviderApiManagerBase { protected Bundle setErrorResult(Bundle result, String stringJsonErrorMessage) { String reasonToFail = pickErrorMessage(stringJsonErrorMessage); + VpnStatus.logWarning("[API] error: " + reasonToFail); result.putString(ERRORS, reasonToFail); result.putBoolean(BROADCAST_RESULT_KEY, false); return result; @@ -815,6 +869,7 @@ public abstract class ProviderApiManagerBase { } else { addErrorMessageToJson(errorJson, errorMessage); } + VpnStatus.logWarning("[API] error: " + errorMessage); result.putString(ERRORS, errorJson.toString()); result.putBoolean(BROADCAST_RESULT_KEY, false); return result; diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderManager.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderManager.java index d33a175f..654fb8e2 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderManager.java @@ -22,12 +22,16 @@ import java.util.Set; import se.leap.bitmaskclient.base.models.Provider; +import static se.leap.bitmaskclient.base.models.Constants.EXT_JSON; +import static se.leap.bitmaskclient.base.models.Constants.EXT_PEM; +import static se.leap.bitmaskclient.base.models.Constants.URLS; import static se.leap.bitmaskclient.base.models.Provider.GEOIP_URL; import static se.leap.bitmaskclient.base.models.Provider.MAIN_URL; import static se.leap.bitmaskclient.base.models.Provider.PROVIDER_API_IP; import static se.leap.bitmaskclient.base.models.Provider.PROVIDER_IP; import static se.leap.bitmaskclient.base.utils.FileHelper.createFile; import static se.leap.bitmaskclient.base.utils.FileHelper.persistFile; +import static se.leap.bitmaskclient.base.utils.InputStreamHelper.extractKeyFromInputStream; import static se.leap.bitmaskclient.base.utils.InputStreamHelper.getInputStreamFrom; import static se.leap.bitmaskclient.base.utils.InputStreamHelper.loadInputStreamAsString; @@ -45,10 +49,6 @@ public class ProviderManager implements AdapteeCollection<Provider> { private static ProviderManager instance; - final private static String URLS = "urls"; - final private static String EXT_JSON = ".json"; - final private static String EXT_PEM = ".pem"; - public static ProviderManager getInstance(AssetManager assetsManager, File externalFilesDir) { if (instance == null) instance = new ProviderManager(assetsManager, externalFilesDir); @@ -138,28 +138,6 @@ public class ProviderManager implements AdapteeCollection<Provider> { return providers; } - private String extractKeyFromInputStream(InputStream inputStream, String key) { - String value = ""; - - JSONObject fileContents = inputStreamToJson(inputStream); - if (fileContents != null) - value = fileContents.optString(key); - return value; - } - - private JSONObject inputStreamToJson(InputStream inputStream) { - JSONObject json = null; - try { - byte[] bytes = new byte[inputStream.available()]; - if (inputStream.read(bytes) > 0) - json = new JSONObject(new String(bytes)); - inputStream.reset(); - } catch (IOException | JSONException e) { - e.printStackTrace(); - } - return json; - } - public List<Provider> providers() { List<Provider> allProviders = new ArrayList<>(); allProviders.addAll(defaultProviders); diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/CustomProviderSetupActivity.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/CustomProviderSetupActivity.java index 161c53d3..b90d14f8 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/CustomProviderSetupActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/CustomProviderSetupActivity.java @@ -17,19 +17,33 @@ package se.leap.bitmaskclient.providersetup.activities; import android.content.Intent; +import android.content.res.AssetManager; import android.os.Bundle; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; + import se.leap.bitmaskclient.BuildConfig; +import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.base.models.Provider; import se.leap.bitmaskclient.providersetup.ProviderAPICommand; -import se.leap.bitmaskclient.R; +import static se.leap.bitmaskclient.BuildConfig.customProviderApiIp; +import static se.leap.bitmaskclient.BuildConfig.customProviderIp; +import static se.leap.bitmaskclient.BuildConfig.customProviderUrl; +import static se.leap.bitmaskclient.BuildConfig.geoipUrl; +import static se.leap.bitmaskclient.base.models.Constants.EXT_JSON; +import static se.leap.bitmaskclient.base.models.Constants.EXT_PEM; import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_CONFIGURE_LEAP; +import static se.leap.bitmaskclient.base.utils.ConfigHelper.preferAnonymousUsage; +import static se.leap.bitmaskclient.base.utils.InputStreamHelper.loadInputStreamAsString; import static se.leap.bitmaskclient.providersetup.ProviderAPI.SET_UP_PROVIDER; import static se.leap.bitmaskclient.providersetup.ProviderSetupInterface.ProviderConfigState.SETTING_UP_PROVIDER; -import static se.leap.bitmaskclient.base.utils.ConfigHelper.preferAnonymousUsage; /** * Created by cyberta on 17.08.18. @@ -42,7 +56,7 @@ public class CustomProviderSetupActivity extends ProviderSetupBaseActivity { super.onCreate(savedInstanceState); setUpInitialUI(); restoreState(savedInstanceState); - setProvider(new Provider(BuildConfig.customProviderUrl, BuildConfig.geoipUrl, BuildConfig.customProviderIp, BuildConfig.customProviderApiIp)); + setDefaultProvider(); } @Override @@ -54,6 +68,21 @@ public class CustomProviderSetupActivity extends ProviderSetupBaseActivity { } } + private void setDefaultProvider() { + try { + AssetManager assetsManager = getAssets(); + Provider customProvider = new Provider(customProviderUrl, geoipUrl, customProviderIp, customProviderApiIp); + String certificate = loadInputStreamAsString(assetsManager.open(customProvider.getDomain() + EXT_PEM)); + String providerDefinition = loadInputStreamAsString(assetsManager.open(customProvider.getDomain() + EXT_JSON)); + customProvider.setCaCert(certificate); + customProvider.define(new JSONObject(providerDefinition)); + setProvider(customProvider); + } catch (IOException | JSONException e) { + e.printStackTrace(); + setProvider(new Provider(customProviderUrl, geoipUrl, customProviderIp, customProviderApiIp)); + } + } + private void setUpInitialUI() { setContentView(R.layout.a_custom_provider_setup); setProviderHeaderText(R.string.setup_provider); diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/DnsResolver.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/DnsResolver.java index c5100a67..5655e7b7 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/DnsResolver.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/DnsResolver.java @@ -7,6 +7,7 @@ import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; +import de.blinkt.openvpn.core.VpnStatus; import okhttp3.Dns; import se.leap.bitmaskclient.base.models.Provider; import se.leap.bitmaskclient.base.models.ProviderObservable; @@ -28,10 +29,12 @@ class DnsResolver implements Dns { } String ip = currentProvider.getIpForHostname(hostname); if (!ip.isEmpty()) { + VpnStatus.logWarning("[API] Normal DNS resolution for " + hostname + " seems to be blocked. Circumventing."); ArrayList<InetAddress> addresses = new ArrayList<>(); addresses.add(InetAddress.getByAddress(hostname, IPAddress.asBytes(ip))); return addresses; } else { + VpnStatus.logWarning("[API] Could not resolve DNS for " + hostname); throw new UnknownHostException("Hostname " + hostname + " not found"); } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0f07ac8e..000effc7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -113,7 +113,7 @@ <string name="hide_experimental">Hide experimental features</string> <string name="tethering_enabled_message">Please make sure to enable tethering in the <![CDATA[<b>system settings</b>]]> first.</string> <string name="tethering_message">Share your VPN with other devices via:</string> - <string name="tethering_wifi">Wifi hotspot</string> + <string name="tethering_wifi">Wi-Fi hotspot</string> <string name="tethering_usb">USB tethering</string> <string name="tethering_bluetooth">Bluetooth tethering</string> <string name="do_not_show_again">Do not show again</string> @@ -140,7 +140,6 @@ <string name="warning_option_try_ovpn">Try standard connection</string> <string name="vpn_error_establish">Android failed to establish the VPN service.</string> <string name="root_permission_error">%s cannot execute features like VPN Hotspot or IPv6 firewall without root permissions.</string> - <string name="qs_enable_vpn">Start %s</string> <string name="version_update_found">Tap here to start the download.</string> <string name="version_update_title">A new %s version has been found.</string> diff --git a/app/src/production/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java b/app/src/production/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java index 592db085..96d8ea69 100644 --- a/app/src/production/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java +++ b/app/src/production/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java @@ -22,8 +22,6 @@ import android.content.res.Resources; import android.os.Bundle; import android.util.Pair; -import androidx.multidex.BuildConfig; - import org.json.JSONException; import org.json.JSONObject; @@ -34,25 +32,26 @@ import java.util.List; import de.blinkt.openvpn.core.VpnStatus; import okhttp3.OkHttpClient; import se.leap.bitmaskclient.R; -import se.leap.bitmaskclient.eip.EIP; import se.leap.bitmaskclient.base.models.Provider; -import se.leap.bitmaskclient.providersetup.connectivity.OkHttpClientGenerator; import se.leap.bitmaskclient.base.utils.ConfigHelper; +import se.leap.bitmaskclient.eip.EIP; +import se.leap.bitmaskclient.providersetup.connectivity.OkHttpClientGenerator; import static android.text.TextUtils.isEmpty; -import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY; -import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY; -import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_VPN_CERTIFICATE; -import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORS; -import static se.leap.bitmaskclient.providersetup.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_CERTIFICATE_PINNING; -import static se.leap.bitmaskclient.providersetup.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_CORRUPTED_PROVIDER_JSON; +import static se.leap.bitmaskclient.BuildConfig.DEBUG_MODE; import static se.leap.bitmaskclient.R.string.downloading_vpn_certificate_failed; import static se.leap.bitmaskclient.R.string.error_io_exception_user_message; import static se.leap.bitmaskclient.R.string.malformed_url; import static se.leap.bitmaskclient.R.string.setup_error_text; import static se.leap.bitmaskclient.R.string.warning_corrupted_provider_cert; import static se.leap.bitmaskclient.R.string.warning_corrupted_provider_details; +import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY; +import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY; +import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_VPN_CERTIFICATE; import static se.leap.bitmaskclient.base.utils.ConfigHelper.getProviderFormattedString; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORS; +import static se.leap.bitmaskclient.providersetup.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_CERTIFICATE_PINNING; +import static se.leap.bitmaskclient.providersetup.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_CORRUPTED_PROVIDER_JSON; /** * Implements the logic of the provider api http requests. The methods of this class need to be called from @@ -88,6 +87,7 @@ public class ProviderApiManager extends ProviderApiManagerBase { if (isEmpty(provider.getMainUrlString()) || provider.getMainUrl().isDefault()) { currentDownload.putBoolean(BROADCAST_RESULT_KEY, false); setErrorResult(currentDownload, malformed_url, null); + VpnStatus.logWarning("[API] MainURL String is not set. Cannot setup provider."); return currentDownload; } @@ -139,8 +139,8 @@ public class ProviderApiManager extends ProviderApiManagerBase { return result; } - if (BuildConfig.DEBUG) { - VpnStatus.logDebug("PROVIDER JSON: " + providerDotJsonString); + if (DEBUG_MODE) { + VpnStatus.logDebug("[API] PROVIDER JSON: " + providerDotJsonString); } try { JSONObject providerJson = new JSONObject(providerDotJsonString); @@ -168,10 +168,10 @@ public class ProviderApiManager extends ProviderApiManagerBase { try { String eipServiceUrl = provider.getApiUrlWithVersion() + "/" + EIP.SERVICE_API_PATH; eipServiceJsonString = downloadWithProviderCA(provider.getCaCert(), eipServiceUrl); - JSONObject eipServiceJson = new JSONObject(eipServiceJsonString); - if (BuildConfig.DEBUG) { - VpnStatus.logDebug("EIP SERVICE JSON: " + eipServiceJsonString); + if (DEBUG_MODE) { + VpnStatus.logDebug("[API] EIP SERVICE JSON: " + eipServiceJsonString); } + JSONObject eipServiceJson = new JSONObject(eipServiceJsonString); if (eipServiceJson.has(ERRORS)) { setErrorResult(result, eipServiceJsonString); } else { @@ -197,8 +197,8 @@ public class ProviderApiManager extends ProviderApiManagerBase { URL newCertStringUrl = new URL(provider.getApiUrlWithVersion() + "/" + PROVIDER_VPN_CERTIFICATE); String certString = downloadWithProviderCA(provider.getCaCert(), newCertStringUrl.toString()); - if (BuildConfig.DEBUG) { - VpnStatus.logDebug("VPN CERT: " + certString); + if (DEBUG_MODE) { + VpnStatus.logDebug("[API] VPN CERT: " + certString); } if (ConfigHelper.checkErroneousDownload(certString)) { if (certString == null || certString.isEmpty()) { @@ -240,6 +240,9 @@ public class ProviderApiManager extends ProviderApiManagerBase { URL geoIpUrl = provider.getGeoipUrl().getUrl(); String geoipJsonString = downloadFromUrlWithProviderCA(geoIpUrl.toString(), provider); + if (DEBUG_MODE) { + VpnStatus.logDebug("[API] MENSHEN JSON: " + geoipJsonString); + } JSONObject geoipJson = new JSONObject(geoipJsonString); if (geoipJson.has(ERRORS)) { @@ -267,10 +270,10 @@ public class ProviderApiManager extends ProviderApiManagerBase { if (validCertificate(provider, certString)) { provider.setCaCert(certString); - preferences.edit().putString(Provider.CA_CERT + "." + providerDomain, certString).apply(); - if (BuildConfig.DEBUG) { - VpnStatus.logDebug("CA CERT: " + certString); + if (DEBUG_MODE) { + VpnStatus.logDebug("[API] CA CERT: " + certString); } + preferences.edit().putString(Provider.CA_CERT + "." + providerDomain, certString).apply(); result.putBoolean(BROADCAST_RESULT_KEY, true); } else { setErrorResult(result, warning_corrupted_provider_cert, ERROR_CERTIFICATE_PINNING.toString()); diff --git a/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java b/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java index d4b7c5d1..5a341cd1 100644 --- a/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java +++ b/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java @@ -366,6 +366,8 @@ public class MockHelper { return getClass().getClassLoader().getResourceAsStream(filename); } }); + when(InputStreamHelper.extractKeyFromInputStream(any(InputStream.class), anyString())).thenCallRealMethod(); + when(InputStreamHelper.inputStreamToJson(any(InputStream.class))).thenCallRealMethod(); } diff --git a/build.gradle b/build.gradle index bb336511..a995fd04 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.4.1' + classpath 'com.android.tools.build:gradle:4.1.2' } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e264661d..5e689c00 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sun May 19 00:15:53 CEST 2019 +#Thu Feb 11 23:53:25 CET 2021 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip |