diff options
Diffstat (limited to 'app/src/main/java/se/leap')
11 files changed, 377 insertions, 209 deletions
diff --git a/app/src/main/java/se/leap/bitmaskclient/EipFragment.java b/app/src/main/java/se/leap/bitmaskclient/EipFragment.java index 9fcdcac9..5dbec7b0 100644 --- a/app/src/main/java/se/leap/bitmaskclient/EipFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/EipFragment.java @@ -29,7 +29,6 @@ import android.os.Bundle; import android.os.IBinder; import android.support.annotation.NonNull; import android.support.v4.app.Fragment; -import android.support.v4.content.LocalBroadcastManager; import android.support.v7.app.AlertDialog; import android.support.v7.widget.AppCompatImageView; import android.support.v7.widget.AppCompatTextView; @@ -56,17 +55,12 @@ import se.leap.bitmaskclient.views.VpnStateImage; import static android.view.View.GONE; import static android.view.View.VISIBLE; import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_NONETWORK; -import static se.leap.bitmaskclient.Constants.BROADCAST_EIP_EVENT; -import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_CODE; -import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_KEY; -import static se.leap.bitmaskclient.Constants.EIP_ACTION_STOP; -import static se.leap.bitmaskclient.Constants.EIP_REQUEST; import static se.leap.bitmaskclient.Constants.EIP_RESTART_ON_BOOT; import static se.leap.bitmaskclient.Constants.PROVIDER_KEY; import static se.leap.bitmaskclient.Constants.REQUEST_CODE_LOG_IN; import static se.leap.bitmaskclient.Constants.REQUEST_CODE_SWITCH_PROVIDER; import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; -import static se.leap.bitmaskclient.ProviderAPI.DOWNLOAD_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.ProviderAPI.UPDATE_INVALID_VPN_CERTIFICATE; import static se.leap.bitmaskclient.ProviderAPI.USER_MESSAGE; import static se.leap.bitmaskclient.R.string.vpn_certificate_user_message; @@ -240,7 +234,7 @@ public class EipFragment extends Fragment implements Observer { askUserToLogIn(getString(vpn_certificate_user_message)); } else { // provider has no VpnCertificate but user is logged in - downloadVpnCertificate(); + updateInvalidVpnCertificate(); } } @@ -280,24 +274,11 @@ public class EipFragment extends Fragment implements Observer { protected void stopEipIfPossible() { Context context = getContext(); - if (context != null) { - if (isOpenVpnRunningWithoutNetwork()) { - // TODO move to EIP - // TODO see stopEIP function - Bundle resultData = new Bundle(); - resultData.putString(EIP_REQUEST, EIP_ACTION_STOP); - Intent intentUpdate = new Intent(BROADCAST_EIP_EVENT); - intentUpdate.addCategory(Intent.CATEGORY_DEFAULT); - intentUpdate.putExtra(BROADCAST_RESULT_CODE, Activity.RESULT_OK); - intentUpdate.putExtra(BROADCAST_RESULT_KEY, resultData); - Log.d(TAG, "sending broadcast"); - LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(intentUpdate); - } else { - EipCommand.stopVPN(getContext()); - } - } else { + if (context == null) { Log.e(TAG, "context is null when trying to stop EIP"); + return; } + EipCommand.stopVPN(context); } private void askPendingStartCancellation() { @@ -462,8 +443,8 @@ public class EipFragment extends Fragment implements Observer { background.setImageAlpha(210); } - private void downloadVpnCertificate() { - ProviderAPICommand.execute(getContext(), DOWNLOAD_VPN_CERTIFICATE, provider); + private void updateInvalidVpnCertificate() { + ProviderAPICommand.execute(getContext(), UPDATE_INVALID_VPN_CERTIFICATE, provider); } private void askUserToLogIn(String userMessage) { diff --git a/app/src/main/java/se/leap/bitmaskclient/MainActivity.java b/app/src/main/java/se/leap/bitmaskclient/MainActivity.java index c194ea99..c44e8a3e 100644 --- a/app/src/main/java/se/leap/bitmaskclient/MainActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/MainActivity.java @@ -18,15 +18,11 @@ package se.leap.bitmaskclient; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.ServiceConnection; import android.content.SharedPreferences; import android.os.Bundle; -import android.os.IBinder; -import android.os.RemoteException; import android.support.v4.app.DialogFragment; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; @@ -39,17 +35,8 @@ import android.util.Log; import org.json.JSONException; import org.json.JSONObject; -import java.util.Observable; -import java.util.Observer; - -import de.blinkt.openvpn.core.IOpenVPNServiceInternal; -import de.blinkt.openvpn.core.OpenVPNService; -import de.blinkt.openvpn.core.ProfileManager; -import de.blinkt.openvpn.core.VpnStatus; import se.leap.bitmaskclient.drawer.NavigationDrawerFragment; import se.leap.bitmaskclient.eip.EipCommand; -import se.leap.bitmaskclient.eip.EipStatus; -import se.leap.bitmaskclient.eip.VoidVpnService; import se.leap.bitmaskclient.fragments.LogFragment; import se.leap.bitmaskclient.utils.ConfigHelper; @@ -60,9 +47,7 @@ import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_CODE; import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_KEY; import static se.leap.bitmaskclient.Constants.EIP_ACTION_START; import static se.leap.bitmaskclient.Constants.EIP_ACTION_STOP; -import static se.leap.bitmaskclient.Constants.EIP_ACTION_STOP_BLOCKING_VPN; import static se.leap.bitmaskclient.Constants.EIP_REQUEST; -import static se.leap.bitmaskclient.Constants.EIP_RESTART_ON_BOOT; import static se.leap.bitmaskclient.Constants.PROVIDER_KEY; import static se.leap.bitmaskclient.Constants.REQUEST_CODE_CONFIGURE_LEAP; import static se.leap.bitmaskclient.Constants.REQUEST_CODE_LOG_IN; @@ -70,10 +55,10 @@ import static se.leap.bitmaskclient.Constants.REQUEST_CODE_SWITCH_PROVIDER; import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; import static se.leap.bitmaskclient.EipFragment.ASK_TO_CANCEL_VPN; import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_DOWNLOADED_EIP_SERVICE; -import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_DOWNLOADED_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE; import static se.leap.bitmaskclient.ProviderAPI.ERRORS; import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_DOWNLOADED_EIP_SERVICE; -import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE; import static se.leap.bitmaskclient.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; @@ -81,31 +66,15 @@ import static se.leap.bitmaskclient.utils.PreferenceHelper.getSavedProviderFromS import static se.leap.bitmaskclient.utils.PreferenceHelper.storeProviderInPreferences; -public class MainActivity extends AppCompatActivity implements Observer { +public class MainActivity extends AppCompatActivity { public final static String TAG = MainActivity.class.getSimpleName(); private Provider provider = new Provider(); private SharedPreferences preferences; - private EipStatus eipStatus; private NavigationDrawerFragment navigationDrawerFragment; private MainActivityBroadcastReceiver mainActivityBroadcastReceiver; - private IOpenVPNServiceInternal mService; - private ServiceConnection openVpnConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName className, - IBinder service) { - mService = IOpenVPNServiceInternal.Stub.asInterface(service); - } - - @Override - public void onServiceDisconnected(ComponentName arg0) { - mService = null; - } - - }; - public final static String ACTION_SHOW_VPN_FRAGMENT = "action_show_vpn_fragment"; public final static String ACTION_SHOW_LOG_FRAGMENT = "action_show_log_fragment"; @@ -133,14 +102,12 @@ public class MainActivity extends AppCompatActivity implements Observer { R.id.navigation_drawer, (DrawerLayout) findViewById(R.id.drawer_layout)); - eipStatus = EipStatus.getInstance(); handleIntentAction(getIntent()); } @Override protected void onResume() { super.onResume(); - bindOpenVpnService(); } @Override @@ -242,7 +209,6 @@ public class MainActivity extends AppCompatActivity implements Observer { @Override protected void onPause() { super.onPause(); - unbindService(openVpnConnection); } @Override @@ -252,14 +218,6 @@ public class MainActivity extends AppCompatActivity implements Observer { super.onDestroy(); } - - @Override - public void update(Observable observable, Object data) { - if (observable instanceof EipStatus) { - eipStatus = (EipStatus) observable; - } - } - private void setUpBroadcastReceiver() { IntentFilter updateIntentFilter = new IntentFilter(BROADCAST_EIP_EVENT); updateIntentFilter.addAction(BROADCAST_PROVIDER_API_EVENT); @@ -320,7 +278,6 @@ public class MainActivity extends AppCompatActivity implements Observer { case EIP_ACTION_STOP: switch (resultCode) { case RESULT_OK: - stop(); break; case RESULT_CANCELED: break; @@ -340,12 +297,12 @@ public class MainActivity extends AppCompatActivity implements Observer { // TODO CATCH ME IF YOU CAN - WHAT DO WE WANT TO DO? break; - case CORRECTLY_DOWNLOADED_VPN_CERTIFICATE: + case CORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE: provider = resultData.getParcelable(PROVIDER_KEY); storeProviderInPreferences(preferences, provider); EipCommand.startVPN(this, true); break; - case INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE: + case INCORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE: if (LeapSRPSession.loggedIn() || provider.allowsAnonymous()) { showMainActivityErrorDialog(getString(downloading_vpn_certificate_failed)); } else { @@ -381,38 +338,6 @@ public class MainActivity extends AppCompatActivity implements Observer { } - private void stop() { - preferences.edit().putBoolean(EIP_RESTART_ON_BOOT, false).apply(); - if (eipStatus.isBlockingVpnEstablished()) { - stopBlockingVpn(); - } - disconnect(); - } - - private void stopBlockingVpn() { - Log.d(TAG, "stop VoidVpn!"); - Intent stopVoidVpnIntent = new Intent(this, VoidVpnService.class); - stopVoidVpnIntent.setAction(EIP_ACTION_STOP_BLOCKING_VPN); - startService(stopVoidVpnIntent); - } - - private void disconnect() { - ProfileManager.setConntectedVpnProfileDisconnected(this); - if (mService != null) { - try { - mService.stopVPN(false); - } catch (RemoteException e) { - VpnStatus.logException(e); - } - } - } - - private void bindOpenVpnService() { - Intent intent = new Intent(this, OpenVPNService.class); - intent.setAction(OpenVPNService.START_SERVICE); - bindService(intent, openVpnConnection, Context.BIND_AUTO_CREATE); - } - private void askUserToLogIn(String userMessage) { Intent intent = new Intent(this, LoginActivity.class); intent.putExtra(PROVIDER_KEY, provider); diff --git a/app/src/main/java/se/leap/bitmaskclient/MainActivityErrorDialog.java b/app/src/main/java/se/leap/bitmaskclient/MainActivityErrorDialog.java index 23bc8427..1065503b 100644 --- a/app/src/main/java/se/leap/bitmaskclient/MainActivityErrorDialog.java +++ b/app/src/main/java/se/leap/bitmaskclient/MainActivityErrorDialog.java @@ -28,7 +28,7 @@ import org.json.JSONObject; import static se.leap.bitmaskclient.MainActivityErrorDialog.DOWNLOAD_ERRORS.DEFAULT; import static se.leap.bitmaskclient.MainActivityErrorDialog.DOWNLOAD_ERRORS.valueOf; -import static se.leap.bitmaskclient.ProviderAPI.DOWNLOAD_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.ProviderAPI.UPDATE_INVALID_VPN_CERTIFICATE; import static se.leap.bitmaskclient.eip.EIP.ERRORS; import static se.leap.bitmaskclient.eip.EIP.ERROR_ID; @@ -107,7 +107,7 @@ public class MainActivityErrorDialog extends DialogFragment { builder.setPositiveButton(R.string.update_certificate, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - ProviderAPICommand.execute(getContext(), DOWNLOAD_VPN_CERTIFICATE, provider); + ProviderAPICommand.execute(getContext(), UPDATE_INVALID_VPN_CERTIFICATE, provider); } }); break; diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderAPI.java b/app/src/main/java/se/leap/bitmaskclient/ProviderAPI.java index f1f474d7..0e27592b 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderAPI.java +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderAPI.java @@ -45,6 +45,7 @@ public class ProviderAPI extends IntentService implements ProviderApiManagerBase LOG_IN = "srpAuth", LOG_OUT = "logOut", DOWNLOAD_VPN_CERTIFICATE = "downloadUserAuthedVPNCertificate", + UPDATE_INVALID_VPN_CERTIFICATE = "ProviderAPI.UPDATE_INVALID_VPN_CERTIFICATE", PARAMETERS = "parameters", RECEIVER_KEY = "receiver", ERRORS = "errors", @@ -67,7 +68,9 @@ public class ProviderAPI extends IntentService implements ProviderApiManagerBase PROVIDER_OK = 11, PROVIDER_NOK = 12, CORRECTLY_DOWNLOADED_EIP_SERVICE = 13, - INCORRECTLY_DOWNLOADED_EIP_SERVICE = 14; + INCORRECTLY_DOWNLOADED_EIP_SERVICE = 14, + CORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE = 15, + INCORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE = 16; ProviderApiManager providerApiManager; diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java b/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java index 25b5a56c..8f3acf1d 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java @@ -67,6 +67,7 @@ import static se.leap.bitmaskclient.ProviderAPI.BACKEND_ERROR_KEY; import static se.leap.bitmaskclient.ProviderAPI.BACKEND_ERROR_MESSAGE; import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_DOWNLOADED_EIP_SERVICE; import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_DOWNLOADED_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE; import static se.leap.bitmaskclient.ProviderAPI.DOWNLOAD_SERVICE_JSON; import static se.leap.bitmaskclient.ProviderAPI.DOWNLOAD_VPN_CERTIFICATE; import static se.leap.bitmaskclient.ProviderAPI.ERRORID; @@ -75,6 +76,7 @@ import static se.leap.bitmaskclient.ProviderAPI.FAILED_LOGIN; import static se.leap.bitmaskclient.ProviderAPI.FAILED_SIGNUP; import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_DOWNLOADED_EIP_SERVICE; import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE; import static se.leap.bitmaskclient.ProviderAPI.LOGOUT_FAILED; import static se.leap.bitmaskclient.ProviderAPI.LOG_IN; import static se.leap.bitmaskclient.ProviderAPI.LOG_OUT; @@ -88,6 +90,7 @@ import static se.leap.bitmaskclient.ProviderAPI.SIGN_UP; import static se.leap.bitmaskclient.ProviderAPI.SUCCESSFUL_LOGIN; import static se.leap.bitmaskclient.ProviderAPI.SUCCESSFUL_LOGOUT; import static se.leap.bitmaskclient.ProviderAPI.SUCCESSFUL_SIGNUP; +import static se.leap.bitmaskclient.ProviderAPI.UPDATE_INVALID_VPN_CERTIFICATE; import static se.leap.bitmaskclient.ProviderAPI.UPDATE_PROVIDER_DETAILS; import static se.leap.bitmaskclient.ProviderAPI.USER_MESSAGE; import static se.leap.bitmaskclient.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_CERTIFICATE_PINNING; @@ -201,6 +204,14 @@ public abstract class ProviderApiManagerBase { sendToReceiverOrBroadcast(receiver, INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE, result, provider); } break; + case UPDATE_INVALID_VPN_CERTIFICATE: + result = updateVpnCertificate(provider); + if (result.getBoolean(BROADCAST_RESULT_KEY)) { + sendToReceiverOrBroadcast(receiver, CORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE, result, provider); + } else { + sendToReceiverOrBroadcast(receiver, INCORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE, result, provider); + } + break; case DOWNLOAD_SERVICE_JSON: result = getAndSetEipServiceJson(provider); if (result.getBoolean(BROADCAST_RESULT_KEY)) { diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderListBaseActivity.java b/app/src/main/java/se/leap/bitmaskclient/ProviderListBaseActivity.java index e0d6e27d..6a0a1864 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderListBaseActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderListBaseActivity.java @@ -385,7 +385,8 @@ public abstract class ProviderListBaseActivity extends ConfigWizardBaseActivity Bundle resultData = intent.getParcelableExtra(BROADCAST_RESULT_KEY); Provider handledProvider = resultData.getParcelable(PROVIDER_KEY); - if (handledProvider != null && handledProvider.getDomain().equalsIgnoreCase(provider.getDomain())) { + if (handledProvider != null && provider != null && + handledProvider.getDomain().equalsIgnoreCase(provider.getDomain())) { switch (resultCode) { case PROVIDER_OK: handleProviderSetUp(handledProvider); 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 665e0ebd..971d973f 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java @@ -16,20 +16,38 @@ */ package se.leap.bitmaskclient.eip; -import android.app.IntentService; +import android.annotation.SuppressLint; +import android.content.ComponentName; +import android.content.Context; import android.content.Intent; +import android.content.ServiceConnection; import android.content.SharedPreferences; import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; import android.os.ResultReceiver; +import android.support.annotation.NonNull; +import android.support.annotation.StringRes; +import android.support.annotation.WorkerThread; +import android.support.v4.app.JobIntentService; import android.support.v4.content.LocalBroadcastManager; import android.util.Log; import org.json.JSONException; import org.json.JSONObject; +import java.io.Closeable; import java.lang.ref.WeakReference; +import java.util.Observable; +import java.util.Observer; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; import de.blinkt.openvpn.LaunchVPN; +import de.blinkt.openvpn.core.IOpenVPNServiceInternal; +import de.blinkt.openvpn.core.OpenVPNService; +import de.blinkt.openvpn.core.ProfileManager; +import de.blinkt.openvpn.core.VpnStatus; import se.leap.bitmaskclient.OnBootReceiver; import static android.app.Activity.RESULT_CANCELED; @@ -43,57 +61,95 @@ import static se.leap.bitmaskclient.Constants.EIP_ACTION_IS_RUNNING; import static se.leap.bitmaskclient.Constants.EIP_ACTION_START; import static se.leap.bitmaskclient.Constants.EIP_ACTION_START_ALWAYS_ON_VPN; import static se.leap.bitmaskclient.Constants.EIP_ACTION_STOP; +import static se.leap.bitmaskclient.Constants.EIP_ACTION_STOP_BLOCKING_VPN; import static se.leap.bitmaskclient.Constants.EIP_EARLY_ROUTES; import static se.leap.bitmaskclient.Constants.EIP_RECEIVER; import static se.leap.bitmaskclient.Constants.EIP_REQUEST; import static se.leap.bitmaskclient.Constants.EIP_RESTART_ON_BOOT; -import static se.leap.bitmaskclient.Constants.PROVIDER_EIP_DEFINITION; import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE; import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; import static se.leap.bitmaskclient.MainActivityErrorDialog.DOWNLOAD_ERRORS.ERROR_INVALID_VPN_CERTIFICATE; import static se.leap.bitmaskclient.R.string.vpn_certificate_is_invalid; +import static se.leap.bitmaskclient.utils.ConfigHelper.ensureNotOnMainThread; /** * EIP is the abstract base class for interacting with and managing the Encrypted * Internet Proxy connection. Connections are started, stopped, and queried through - * this IntentService. + * this Service. * Contains logic for parsing eip-service.json from the provider, configuring and selecting * gateways, and controlling {@link de.blinkt.openvpn.core.OpenVPNService} connections. * * @author Sean Leonard <meanderingcode@aetherislands.net> * @author Parménides GV <parmegv@sdf.org> */ -public final class EIP extends IntentService { +public final class EIP extends JobIntentService implements Observer { + public final static String TAG = EIP.class.getSimpleName(), SERVICE_API_PATH = "config/eip-service.json", ERRORS = "errors", ERROR_ID = "errorID"; - private WeakReference<ResultReceiver> mReceiverRef = new WeakReference<>(null); - private SharedPreferences preferences; + private volatile SharedPreferences preferences; + private volatile EipStatus eipStatus; + // Service connection to OpenVpnService, shared between threads + private volatile OpenVpnServiceConnection openVpnServiceConnection; + private WeakReference<ResultReceiver> mResultRef = new WeakReference<>(null); + + /** + * Unique job ID for this service. + */ + static final int JOB_ID = 1312; - public EIP() { - super(TAG); + /** + * Convenience method for enqueuing work in to this service. + */ + static void enqueueWork(Context context, Intent work) { + enqueueWork(context, EIP.class, JOB_ID, work); } @Override public void onCreate() { super.onCreate(); + eipStatus = EipStatus.getInstance(); + eipStatus.addObserver(this); preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); } @Override - protected void onHandleIntent(Intent intent) { - String action = intent.getAction(); - if (intent.getParcelableExtra(EIP_RECEIVER) != null) { - mReceiverRef = new WeakReference<>((ResultReceiver) intent.getParcelableExtra(EIP_RECEIVER)); + public void onDestroy() { + super.onDestroy(); + eipStatus.deleteObserver(this); + if (openVpnServiceConnection != null) { + openVpnServiceConnection.close(); + openVpnServiceConnection = null; + } + } + + /** + * update eipStatus whenever it changes + */ + @Override + public void update(Observable observable, Object data) { + if (observable instanceof EipStatus) { + eipStatus = (EipStatus) observable; } + } + /** + * + * @param intent the intent that started this EIP call + */ + @Override + protected void onHandleWork(@NonNull Intent intent) { + final String action = intent.getAction(); if (action == null) { return; } + if (intent.getParcelableExtra(EIP_RECEIVER) != null) { + mResultRef = new WeakReference<>((ResultReceiver) intent.getParcelableExtra(EIP_RECEIVER)); + } switch (action) { case EIP_ACTION_START: boolean earlyRoutes = intent.getBooleanExtra(EIP_EARLY_ROUTES, true); @@ -119,21 +175,21 @@ public final class EIP extends IntentService { * Intent to {@link de.blinkt.openvpn.LaunchVPN}. * It also sets up early routes. */ + @SuppressLint("ApplySharedPref") private void startEIP(boolean earlyRoutes) { - if (!EipStatus.getInstance().isBlockingVpnEstablished() && earlyRoutes) { + if (!eipStatus.isBlockingVpnEstablished() && earlyRoutes) { earlyRoutes(); } Bundle result = new Bundle(); - - if (!preferences.getBoolean(EIP_RESTART_ON_BOOT, false)){ + if (!preferences.getBoolean(EIP_RESTART_ON_BOOT, false)) { preferences.edit().putBoolean(EIP_RESTART_ON_BOOT, true).commit(); } GatewaysManager gatewaysManager = gatewaysFromPreferences(); - if (!isVPNCertificateValid()){ + if (!isVPNCertificateValid()) { setErrorResult(result, vpn_certificate_is_invalid, ERROR_INVALID_VPN_CERTIFICATE.toString()); - tellToReceiverOrBroadcast(EIP_ACTION_START, RESULT_CANCELED, result); + tellToReceiverOrBroadcast(EIP_ACTION_START, RESULT_CANCELED); return; } @@ -173,6 +229,11 @@ public final class EIP extends IntentService { startActivity(voidVpnLauncher); } + /** + * starts the VPN and connects to the given gateway + * + * @param gateway to connect to + */ private void launchActiveGateway(Gateway gateway) { Intent intent = new Intent(this, LaunchVPN.class); intent.setAction(Intent.ACTION_MAIN); @@ -182,14 +243,13 @@ public final class EIP extends IntentService { startActivity(intent); } + /** + * Stop VPN + * First checks if the OpenVpnConnection is open then + * terminates EIP if currently connected or connecting + */ private void stopEIP() { - // TODO stop eip from here if possible... - // TODO then refactor EipFragment.handleSwitchOff - EipStatus eipStatus = EipStatus.getInstance(); - int resultCode = RESULT_CANCELED; - if (eipStatus.isConnected() || eipStatus.isConnecting()) - resultCode = RESULT_OK; - + int resultCode = stop() ? RESULT_OK : RESULT_CANCELED; tellToReceiverOrBroadcast(EIP_ACTION_STOP, resultCode); } @@ -199,36 +259,27 @@ public final class EIP extends IntentService { * request if it's not connected, <code>Activity.RESULT_OK</code> otherwise. */ private void isRunning() { - EipStatus eipStatus = EipStatus.getInstance(); int resultCode = (eipStatus.isConnected()) ? RESULT_OK : RESULT_CANCELED; tellToReceiverOrBroadcast(EIP_ACTION_IS_RUNNING, resultCode); } - private JSONObject eipDefinitionFromPreferences() { - JSONObject result = new JSONObject(); - try { - String eipDefinitionString = preferences.getString(PROVIDER_EIP_DEFINITION, ""); - if (!eipDefinitionString.isEmpty()) { - result = new JSONObject(eipDefinitionString); - } - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - return result; - } - + /** + * read eipServiceJson from preferences and parse Gateways + * + * @return GatewaysManager + */ private GatewaysManager gatewaysFromPreferences() { GatewaysManager gatewaysManager = new GatewaysManager(this, preferences); - //TODO: THIS IS A QUICK FIX - it deletes all profiles in ProfileManager, thus it's possible - // to add all gateways from prefs without duplicates, but this should be refactored. - gatewaysManager.clearGatewaysAndProfiles(); - gatewaysManager.fromEipServiceJson(eipDefinitionFromPreferences()); + gatewaysManager.configureFromPreferences(); return gatewaysManager; } + /** + * read VPN certificate from preferences and check it + * broadcast result + */ private void checkVPNCertificateValidity() { int resultCode = isVPNCertificateValid() ? RESULT_OK : @@ -236,25 +287,51 @@ public final class EIP extends IntentService { tellToReceiverOrBroadcast(EIP_ACTION_CHECK_CERT_VALIDITY, resultCode); } + /** + * read VPN certificate from preferences and check it + * + * @return true if VPN certificate is valid false otherwise + */ private boolean isVPNCertificateValid() { VpnCertificateValidator validator = new VpnCertificateValidator(preferences.getString(PROVIDER_VPN_CERTIFICATE, "")); return validator.isValid(); } + /** + * send resultCode and resultData to receiver or + * broadcast the result if no receiver is defined + * + * @param action the action that has been performed + * @param resultCode RESULT_OK if action was successful RESULT_CANCELED otherwise + * @param resultData other data to broadcast or return to receiver + */ private void tellToReceiverOrBroadcast(String action, int resultCode, Bundle resultData) { resultData.putString(EIP_REQUEST, action); - if (mReceiverRef.get() != null) { - mReceiverRef.get().send(resultCode, resultData); + if (mResultRef.get() != null) { + mResultRef.get().send(resultCode, resultData); } else { broadcastEvent(resultCode, resultData); } } + /** + * send resultCode and resultData to receiver or + * broadcast the result if no receiver is defined + * + * @param action the action that has been performed + * @param resultCode RESULT_OK if action was successful RESULT_CANCELED otherwise + */ private void tellToReceiverOrBroadcast(String action, int resultCode) { tellToReceiverOrBroadcast(action, resultCode, new Bundle()); } - private void broadcastEvent(int resultCode , Bundle resultData) { + /** + * broadcast result + * + * @param resultCode RESULT_OK if action was successful RESULT_CANCELED otherwise + * @param resultData other data to broadcast or return to receiver + */ + private void broadcastEvent(int resultCode, Bundle resultData) { Intent intentUpdate = new Intent(BROADCAST_EIP_EVENT); intentUpdate.addCategory(CATEGORY_DEFAULT); intentUpdate.putExtra(BROADCAST_RESULT_CODE, resultCode); @@ -263,20 +340,133 @@ public final class EIP extends IntentService { LocalBroadcastManager.getInstance(this).sendBroadcast(intentUpdate); } - Bundle setErrorResult(Bundle result, int errorMessageId, String errorId) { + + /** + * helper function to add error to result bundle + * + * @param result - result of an action + * @param errorMessageId - id of string resource describing the error + * @param errorId - MainActivityErrorDialog DownloadError id + */ + void setErrorResult(Bundle result, @StringRes int errorMessageId, String errorId) { JSONObject errorJson = new JSONObject(); - addErrorMessageToJson(errorJson, getResources().getString(errorMessageId), errorId); + try { + errorJson.put(ERRORS, getResources().getString(errorMessageId)); + errorJson.put(ERROR_ID, errorId); + } catch (JSONException e) { + e.printStackTrace(); + } result.putString(ERRORS, errorJson.toString()); result.putBoolean(BROADCAST_RESULT_KEY, false); - return result; } - private void addErrorMessageToJson(JSONObject jsonObject, String errorMessage, String errorId) { + + /** + * disable Bitmask starting on after phone reboot + * then stop VPN + */ + private boolean stop() { + preferences.edit().putBoolean(EIP_RESTART_ON_BOOT, false).apply(); + if (eipStatus.isBlockingVpnEstablished()) { + stopBlockingVpn(); + } + return disconnect(); + } + + /** + * stop void vpn from blocking internet + */ + private void stopBlockingVpn() { + Log.d(TAG, "stop VoidVpn!"); + Intent stopVoidVpnIntent = new Intent(this, VoidVpnService.class); + stopVoidVpnIntent.setAction(EIP_ACTION_STOP_BLOCKING_VPN); + startService(stopVoidVpnIntent); + } + + + /** + * creates a OpenVpnServiceConnection if necessary + * then terminates OpenVPN + */ + private boolean disconnect() { try { - jsonObject.put(ERRORS, errorMessage); - jsonObject.put(ERROR_ID, errorId); - } catch (JSONException e) { - e.printStackTrace(); + initOpenVpnServiceConnection(); + } catch (InterruptedException | IllegalStateException e) { + return false; + } + + ProfileManager.setConntectedVpnProfileDisconnected(this); + try { + return openVpnServiceConnection.getService().stopVPN(false); + } catch (RemoteException e) { + VpnStatus.logException(e); } + return false; } + + /** + * Assigns a new OpenVpnServiceConnection to EIP's member variable openVpnServiceConnection. + * 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 initOpenVpnServiceConnection() throws InterruptedException, IllegalStateException { + if (openVpnServiceConnection == null) { + Log.d(TAG, "serviceConnection is still null"); + openVpnServiceConnection = new OpenVpnServiceConnection(this); + } + } + + /** + * 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 + */ + @WorkerThread + public static class OpenVpnServiceConnection implements Closeable { + private final Context context; + private ServiceConnection serviceConnection; + private IOpenVPNServiceInternal service; + + OpenVpnServiceConnection(Context context) throws InterruptedException, IllegalStateException { + this.context = context; + ensureNotOnMainThread(context); + Log.d(TAG, "initSynchronizedServiceConnection!"); + initSynchronizedServiceConnection(context); + } + + private void initSynchronizedServiceConnection(final Context context) throws InterruptedException { + final BlockingQueue<IOpenVPNServiceInternal> 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 { + blockingQueue.put(IOpenVPNServiceInternal.Stub.asInterface(service)); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + @Override public void onServiceDisconnected(ComponentName name) { + } + }; + + Intent intent = new Intent(context, OpenVPNService.class); + intent.setAction(OpenVPNService.START_SERVICE); + context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE); + service = blockingQueue.take(); + } + + @Override public void close() { + context.unbindService(serviceConnection); + } + + public IOpenVPNServiceInternal getService() { + return service; + } + } + } 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 d2c8b4fc..19735483 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EipCommand.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipCommand.java @@ -5,6 +5,7 @@ import android.content.Context; import android.content.Intent; import android.os.ResultReceiver; import android.support.annotation.NonNull; +import android.support.annotation.VisibleForTesting; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -41,7 +42,7 @@ public class EipCommand { vpnIntent.setAction(action); if (resultReceiver != null) vpnIntent.putExtra(EIP_RECEIVER, resultReceiver); - context.startService(vpnIntent); + EIP.enqueueWork(context, vpnIntent); } public static void startVPN(@NonNull Context context, boolean earlyRoutes) { @@ -50,6 +51,7 @@ public class EipCommand { execute(context, EIP_ACTION_START, null, baseIntent); } + @VisibleForTesting public static void startVPN(@NonNull Context context, ResultReceiver resultReceiver) { execute(context, EIP_ACTION_START, resultReceiver, null); } @@ -58,6 +60,7 @@ public class EipCommand { execute(context, EIP_ACTION_STOP); } + @VisibleForTesting public static void stopVPN(@NonNull Context context, ResultReceiver resultReceiver) { execute(context, EIP_ACTION_STOP, resultReceiver, null); } @@ -66,6 +69,7 @@ public class EipCommand { execute(context, EIP_ACTION_CHECK_CERT_VALIDITY); } + @VisibleForTesting public static void checkVpnCertificate(@NonNull Context context, ResultReceiver resultReceiver) { execute(context, EIP_ACTION_CHECK_CERT_VALIDITY, resultReceiver, null); } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java index 1bdb53ab..5b4db5af 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java @@ -20,7 +20,6 @@ import android.content.Context; import android.content.SharedPreferences; import com.google.gson.Gson; -import com.google.gson.JsonSyntaxException; import com.google.gson.reflect.TypeToken; import org.json.JSONArray; @@ -37,6 +36,7 @@ import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.Connection; import de.blinkt.openvpn.core.ProfileManager; import se.leap.bitmaskclient.Provider; +import se.leap.bitmaskclient.utils.PreferenceHelper; import static se.leap.bitmaskclient.Constants.PROVIDER_PRIVATE_KEY; import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE; @@ -49,52 +49,49 @@ public class GatewaysManager { private Context context; private SharedPreferences preferences; private List<Gateway> gateways = new ArrayList<>(); - private ProfileManager profile_manager; - private Type list_type = new TypeToken<ArrayList<Gateway>>() { - }.getType(); + private ProfileManager profileManager; + private Type listType = new TypeToken<ArrayList<Gateway>>() {}.getType(); - public GatewaysManager() { - } - - public GatewaysManager(Context context, SharedPreferences preferences) { + GatewaysManager(Context context, SharedPreferences preferences) { this.context = context; this.preferences = preferences; - profile_manager = ProfileManager.getInstance(context); + profileManager = ProfileManager.getInstance(context); } + /** + * select closest Gateway + * @return the closest Gateway + */ public Gateway select() { - GatewaySelector gateway_selector = new GatewaySelector(gateways); - return gateway_selector.select(); + GatewaySelector gatewaySelector = new GatewaySelector(gateways); + return gatewaySelector.select(); } + /** + * check if there are no gateways defined + * @return true if no gateways defined else false + */ public boolean isEmpty() { return gateways.isEmpty(); } + /** + * @return number of gateways defined in the GatewaysManager + */ public int size() { return gateways.size(); } - public void addFromString(String gateways) { - List<Gateway> gateways_list = new ArrayList<>(); - try { - gateways_list = new Gson().fromJson(gateways, list_type); - } catch (JsonSyntaxException e) { - gateways_list.add(new Gson().fromJson(gateways, Gateway.class)); - } - - if (gateways_list != null) { - for (Gateway gateway : gateways_list) - addGateway(gateway); - } - } - @Override public String toString() { - return new Gson().toJson(gateways, list_type); + return new Gson().toJson(gateways, listType); } - public void fromEipServiceJson(JSONObject eipDefinition) { + /** + * parse gateways from eipDefinition + * @param eipDefinition eipServiceJson + */ + void fromEipServiceJson(JSONObject eipDefinition) { try { JSONArray gatewaysDefined = eipDefinition.getJSONArray("gateways"); for (int i = 0; i < gatewaysDefined.length(); i++) { @@ -113,6 +110,11 @@ public class GatewaysManager { } } + /** + * check if a gateway is an OpenVpn gateway + * @param gateway to check + * @return true if gateway is an OpenVpn gateway otherwise false + */ private boolean isOpenVpnGateway(JSONObject gateway) { try { String transport = gateway.getJSONObject("capabilities").getJSONArray("transport").toString(); @@ -137,7 +139,7 @@ public class GatewaysManager { private boolean containsProfileWithSecrets(VpnProfile profile) { boolean result = false; - Collection<VpnProfile> profiles = profile_manager.getProfiles(); + Collection<VpnProfile> profiles = profileManager.getProfiles(); for (VpnProfile aux : profiles) { result = result || sameConnections(profile.mConnections, aux.mConnections) && profile.mClientCertFilename.equalsIgnoreCase(aux.mClientCertFilename) @@ -146,11 +148,11 @@ public class GatewaysManager { return result; } - protected void clearGatewaysAndProfiles() { + void clearGatewaysAndProfiles() { gateways.clear(); - ArrayList<VpnProfile> profiles = new ArrayList<>(profile_manager.getProfiles()); + ArrayList<VpnProfile> profiles = new ArrayList<>(profileManager.getProfiles()); for (VpnProfile profile : profiles) { - profile_manager.removeProfile(context, profile); + profileManager.removeProfile(context, profile); } } @@ -159,42 +161,62 @@ public class GatewaysManager { gateways.add(gateway); VpnProfile profile = gateway.getProfile(); - profile_manager.addProfile(profile); + profileManager.addProfile(profile); } private void removeDuplicatedGateway(Gateway gateway) { Iterator<Gateway> it = gateways.iterator(); - List<Gateway> gateways_to_remove = new ArrayList<>(); + List<Gateway> gatewaysToRemove = new ArrayList<>(); while (it.hasNext()) { Gateway aux = it.next(); if (sameConnections(aux.getProfile().mConnections, gateway.getProfile().mConnections)) { - gateways_to_remove.add(aux); + gatewaysToRemove.add(aux); } } - gateways.removeAll(gateways_to_remove); + gateways.removeAll(gatewaysToRemove); removeDuplicatedProfiles(gateway.getProfile()); } private void removeDuplicatedProfiles(VpnProfile original) { - Collection<VpnProfile> profiles = profile_manager.getProfiles(); - List<VpnProfile> remove_list = new ArrayList<>(); + Collection<VpnProfile> profiles = profileManager.getProfiles(); + List<VpnProfile> removeList = new ArrayList<>(); for (VpnProfile aux : profiles) { - if (sameConnections(original.mConnections, aux.mConnections)) - remove_list.add(aux); + if (sameConnections(original.mConnections, aux.mConnections)) { + removeList.add(aux); + } + } + for (VpnProfile profile : removeList) { + profileManager.removeProfile(context, profile); } - for (VpnProfile profile : remove_list) - profile_manager.removeProfile(context, profile); } + /** + * check if all connections in c1 are also in c2 + * @param c1 array of connections + * @param c2 array of connections + * @return true if all connections of c1 exist in c2 and vice versa + */ private boolean sameConnections(Connection[] c1, Connection[] c2) { - int same_connections = 0; + int sameConnections = 0; for (Connection c1_aux : c1) { for (Connection c2_aux : c2) if (c2_aux.mServerName.equals(c1_aux.mServerName)) { - same_connections++; + sameConnections++; break; } } - return c1.length == c2.length && c1.length == same_connections; + return c1.length == c2.length && c1.length == sameConnections; + } + + /** + * read EipServiceJson from preferences and set gateways + */ + void configureFromPreferences() { + //TODO: THIS IS A QUICK FIX - it deletes all profiles in ProfileManager, thus it's possible + // to add all gateways from prefs without duplicates, but this should be refactored. + clearGatewaysAndProfiles(); + fromEipServiceJson( + PreferenceHelper.getEipDefinitionFromPreferences(preferences) + ); } } diff --git a/app/src/main/java/se/leap/bitmaskclient/utils/ConfigHelper.java b/app/src/main/java/se/leap/bitmaskclient/utils/ConfigHelper.java index bb9fb2d4..5bb637b7 100644 --- a/app/src/main/java/se/leap/bitmaskclient/utils/ConfigHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/utils/ConfigHelper.java @@ -16,6 +16,8 @@ */ package se.leap.bitmaskclient.utils; +import android.content.Context; +import android.os.Looper; import android.support.annotation.NonNull; import org.json.JSONException; @@ -157,5 +159,12 @@ public class ConfigHelper { return byteArrayToHex(byteArray); } + public static void ensureNotOnMainThread(@NonNull Context context) throws IllegalStateException{ + Looper looper = Looper.myLooper(); + if (looper != null && looper == context.getMainLooper()) { + throw new IllegalStateException( + "calling this from your main thread can lead to deadlock"); + } + } } diff --git a/app/src/main/java/se/leap/bitmaskclient/utils/PreferenceHelper.java b/app/src/main/java/se/leap/bitmaskclient/utils/PreferenceHelper.java index 92f025b2..12015dfb 100644 --- a/app/src/main/java/se/leap/bitmaskclient/utils/PreferenceHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/utils/PreferenceHelper.java @@ -2,6 +2,7 @@ package se.leap.bitmaskclient.utils; import android.content.Context; import android.content.SharedPreferences; +import android.os.Looper; import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -9,6 +10,12 @@ import android.support.annotation.Nullable; import org.json.JSONException; import org.json.JSONObject; +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; @@ -217,4 +224,19 @@ public class PreferenceHelper { SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE); return preferences.getBoolean(ALWAYS_ON_SHOW_DIALOG, true); } + + public static JSONObject getEipDefinitionFromPreferences(SharedPreferences preferences) { + JSONObject result = new JSONObject(); + try { + String eipDefinitionString = preferences.getString(PROVIDER_EIP_DEFINITION, ""); + if (!eipDefinitionString.isEmpty()) { + result = new JSONObject(eipDefinitionString); + } + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return result; + } + } |