summaryrefslogtreecommitdiff
path: root/src/se
diff options
context:
space:
mode:
authorParménides GV <parmegv@ma.sdf.org>2013-06-24 19:37:35 +0200
committerParménides GV <parmegv@ma.sdf.org>2013-06-24 19:37:35 +0200
commit50db5b5816476751f39bb7b8adc927562a0cc677 (patch)
treefa8fc602518430137895fbdbaba439f6c3d92321 /src/se
parent195d5ab2d518f6f3960edd3c636e941830c2664d (diff)
parent304b29d6cb85d710756a56bc9c009429b8407d69 (diff)
Merge branch 'feature/eip' into develop
Diffstat (limited to 'src/se')
-rw-r--r--src/se/leap/leapclient/ConfigHelper.java5
-rw-r--r--src/se/leap/leapclient/Dashboard.java296
-rw-r--r--src/se/leap/leapclient/EIP.java481
-rw-r--r--src/se/leap/leapclient/Provider.java3
-rw-r--r--src/se/leap/leapclient/ProviderAPI.java14
-rw-r--r--src/se/leap/openvpn/ConfigParser.java6
-rw-r--r--src/se/leap/openvpn/LaunchVPN.java24
-rw-r--r--src/se/leap/openvpn/OpenVpnService.java28
-rw-r--r--src/se/leap/openvpn/VpnProfile.java21
9 files changed, 785 insertions, 93 deletions
diff --git a/src/se/leap/leapclient/ConfigHelper.java b/src/se/leap/leapclient/ConfigHelper.java
index dd20112c..cab5fdee 100644
--- a/src/se/leap/leapclient/ConfigHelper.java
+++ b/src/se/leap/leapclient/ConfigHelper.java
@@ -53,6 +53,7 @@ public class ConfigHelper {
ALLOWED_ANON = "allow_anonymous",
MAIN_CERT_KEY = "main_cert",
CERT_KEY = "cert",
+ KEY_KEY = "key",
EIP_SERVICE_KEY = "eip",
TYPE_OF_CERTIFICATE = "type_of_certificate",
ANON_CERTIFICATE = "anon_certificate",
@@ -72,7 +73,9 @@ public class ConfigHelper {
PASSWORD_KEY = "password",
ALLOW_REGISTRATION_KEY = "allow_registration",
EIP_SERVICE_API_PATH = "config/eip-service.json",
- ERRORS_KEY = "errors"
+ ERRORS_KEY = "errors",
+ RECEIVER_TAG = "receiverTag",
+ REQUEST_TAG = "requestTag";
;
final public static String NG_1024 =
diff --git a/src/se/leap/leapclient/Dashboard.java b/src/se/leap/leapclient/Dashboard.java
index dd8ee335..3c9df56c 100644
--- a/src/se/leap/leapclient/Dashboard.java
+++ b/src/se/leap/leapclient/Dashboard.java
@@ -6,9 +6,11 @@ import org.json.JSONException;
import org.json.JSONObject;
import se.leap.leapclient.ProviderAPIResultReceiver.Receiver;
-import se.leap.leapclient.R;
import se.leap.openvpn.AboutFragment;
+import se.leap.openvpn.LogWindow;
import se.leap.openvpn.MainActivity;
+import se.leap.openvpn.OpenVPN;
+import se.leap.openvpn.OpenVPN.StateListener;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.DialogFragment;
@@ -20,17 +22,25 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
+import android.os.ResultReceiver;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.widget.CompoundButton;
+import android.widget.RelativeLayout;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
-public class Dashboard extends Activity implements LogInDialog.LogInDialogInterface, Receiver {
+/**
+ * The main user facing Activity of LEAP Android, consisting of status, controls,
+ * and access to preferences.
+ *
+ * @author Sean Leonard <meanderingcode@aetherislands.net>
+ */
+public class Dashboard extends Activity implements LogInDialog.LogInDialogInterface,Receiver,StateListener {
protected static final int CONFIGURE_LEAP = 0;
@@ -40,8 +50,14 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf
private TextView providerNameTV;
private TextView eipTypeTV;
+ private Switch eipSwitch;
+ private View eipDetail;
+ private TextView eipStatus;
+
+ private boolean mEipWait = false;
public ProviderAPIResultReceiver providerAPI_result_receiver;
+ private EIPReceiver mEIPReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -55,89 +71,150 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf
if(ConfigHelper.shared_preferences == null)
ConfigHelper.setSharedPreferences(preferences);
- // Check if we have preferences, run configuration wizard if not
- // TODO We should do a better check for config that this!
if (preferences.contains("provider") && preferences.getString(ConfigHelper.PROVIDER_KEY, null) != null)
buildDashboard();
else
startActivityForResult(new Intent(this,ConfigurationWizard.class),CONFIGURE_LEAP);
}
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ if (provider != null)
+ if (provider.hasEIP() && provider.getEIPType() == "OpenVPN")
+ OpenVPN.removeStateListener(this);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (provider != null)
+ if (provider.hasEIP() && provider.getEIPType() == "OpenVPN")
+ OpenVPN.addStateListener(this);
+ }
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data){
if ( requestCode == CONFIGURE_LEAP ) {
if ( resultCode == RESULT_OK ){
- // Configuration done, get our preferences again
- preferences = getSharedPreferences(ConfigHelper.PREFERENCES_KEY,MODE_PRIVATE);
-
buildDashboard();
- } else {
- // Something went wrong in configuration
- AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getAppContext());
- alertBuilder.setTitle(getResources().getString(R.string.setup_error_title));
- alertBuilder
- .setMessage(getResources().getString(R.string.setup_error_text))
- .setCancelable(false)
- .setPositiveButton(getResources().getString(R.string.setup_error_configure_button), new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- startActivityForResult(new Intent(getAppContext(),ConfigurationWizard.class),CONFIGURE_LEAP);
- }
- })
- .setNegativeButton(getResources().getString(R.string.setup_error_close_button), new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- SharedPreferences.Editor prefsEdit = getSharedPreferences(ConfigHelper.PREFERENCES_KEY, MODE_PRIVATE).edit();
- prefsEdit.remove(ConfigHelper.PROVIDER_KEY).commit();
- finish();
- }
- });
- }
+ } else
+ configErrorDialog();
}
}
+ /**
+ * Dialog shown when encountering a configuration error. Such errors require
+ * reconfiguring LEAP or aborting the application.
+ */
+ private void configErrorDialog() {
+ AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getAppContext());
+ alertBuilder.setTitle(getResources().getString(R.string.setup_error_title));
+ alertBuilder
+ .setMessage(getResources().getString(R.string.setup_error_text))
+ .setCancelable(false)
+ .setPositiveButton(getResources().getString(R.string.setup_error_configure_button), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ startActivityForResult(new Intent(getAppContext(),ConfigurationWizard.class),CONFIGURE_LEAP);
+ }
+ })
+ .setNegativeButton(getResources().getString(R.string.setup_error_close_button), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ SharedPreferences.Editor prefsEdit = getSharedPreferences(ConfigHelper.PREFERENCES_KEY, MODE_PRIVATE).edit();
+ prefsEdit.remove(ConfigHelper.PROVIDER_KEY).commit();
+ finish();
+ }
+ });
+ }
+
+ /**
+ * Inflates permanent UI elements of the View and contains logic for what
+ * service dependent UI elements to include.
+ */
private void buildDashboard() {
- // Get our provider
- provider = Provider.getInstance();
- provider.init( this );
-
- // Set provider name in textview
- providerNameTV = (TextView) findViewById(R.id.providerName);
- providerNameTV.setText(provider.getName());
- providerNameTV.setTextSize(28); // TODO maybe to some calculating, or a marquee?
-
- // TODO Inflate layout fragments for provider's services
- if ( provider.hasEIP() )
- serviceItemEIP();
+ provider = Provider.getInstance();
+ provider.init( this );
+
+ providerNameTV = (TextView) findViewById(R.id.providerName);
+ providerNameTV.setText(provider.getName());
+ providerNameTV.setTextSize(28);
+
+ if ( provider.hasEIP() ){
+ startService( new Intent(EIP.ACTION_UPDATE_EIP_SERVICE) );
+ if (provider.getEIPType() == "OpenVPN")
+ OpenVPN.addStateListener(this);
+ serviceItemEIP();
+ }
}
+ /**
+ * Builds the UI for the EIP service Dashboard component
+ */
private void serviceItemEIP() {
- // FIXME Provider service (eip/openvpn)
+ mEIPReceiver = new EIPReceiver(new Handler());
+ mEIPReceiver.setReceiver(this);
+
+ Intent intent = new Intent(this,EIP.class);
+ intent.setAction(EIP.ACTION_IS_EIP_RUNNING);
+ intent.putExtra(ConfigHelper.RECEIVER_TAG, mEIPReceiver);
+ startService(intent);
+
((ViewStub) findViewById(R.id.eipOverviewStub)).inflate();
- // Set our EIP type title
eipTypeTV = (TextView) findViewById(R.id.eipType);
eipTypeTV.setText(provider.getEIPType());
+
+ eipDetail = ((RelativeLayout) findViewById(R.id.eipDetail));
+ View eipSettings = findViewById(R.id.eipSettings);
+ eipSettings.setVisibility(View.GONE); // FIXME too!
+ eipDetail.setVisibility(View.VISIBLE);
+ eipStatus = (TextView) findViewById(R.id.eipStatus);
- // TODO Bind our switch to run our EIP
- // What happens when our VPN stops running? does it call the listener?
- Switch eipSwitch = (Switch) findViewById(R.id.eipSwitch);
+ eipSwitch = (Switch) findViewById(R.id.eipSwitch);
eipSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- if ( isChecked ){
- //TODO startVPN();
- } else {
- //TODO stopVPN();
+ if (!mEipWait){
+ buttonView.setClickable(false);
+ mEipWait = true;
+
+ Intent vpnIntent;
+ if (isChecked){
+ vpnIntent = new Intent(EIP.ACTION_START_EIP);
+ } else {
+ vpnIntent = new Intent(EIP.ACTION_STOP_EIP);
+ }
+ vpnIntent.putExtra(ConfigHelper.RECEIVER_TAG, mEIPReceiver);
+ startService(vpnIntent);
}
}
});
-
- //TODO write our info into the view fragment that will expand with details and a settings button
- // TODO set eip overview subview
- // TODO make eip type clickable, show subview
- // TODO attach vpn status feedback to eip overview view
- // TODO attach settings button to something
+ }
+
+ /**
+ * Expands the EIP Dashboard component for extra details view.
+ * Called by onClick property in client_dashboard.xml layout.
+ *
+ * @param view (Unused) The View calling this method by its onClick property
+ */
+ public void toggleEipOverview(View view) {
+ if (eipDetail.isShown())
+ eipDetail.setVisibility(View.GONE);
+ else
+ eipDetail.setVisibility(View.VISIBLE);
+ }
+
+ /**
+ * Launches the se.leap.openvpn.LogWindow Activity showing detailed OpenVPN log
+ *
+ * @param view (Unused) The View calling this method by its onClick property
+ */
+ public void showEIPLog(View view){
+ Intent intent = new Intent(getBaseContext(),LogWindow.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+ startActivity(intent);
}
@Override
@@ -159,7 +236,6 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf
@Override
public boolean onCreateOptionsMenu(Menu menu) {
- // Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.client_dashboard, menu);
return true;
}
@@ -167,21 +243,15 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf
@Override
public boolean onOptionsItemSelected(MenuItem item){
Intent intent;
- // Handle item selection
switch (item.getItemId()){
case R.id.about_leap:
- // TODO move se.leap.openvpn.AboutFragment into our package
Fragment aboutFragment = new AboutFragment();
FragmentTransaction trans = getFragmentManager().beginTransaction();
trans.replace(R.id.dashboardLayout, aboutFragment);
trans.addToBackStack(null);
trans.commit();
-
- //intent = new Intent(this,AboutFragment.class);
- //startActivity(intent);
return true;
case R.id.legacy_interface:
- // TODO call se.leap.openvpn.MainActivity
intent = new Intent(this,MainActivity.class);
startActivity(intent);
return true;
@@ -197,11 +267,6 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf
}
}
-
- @SuppressWarnings("unused")
- private void toggleOverview() {
- // TODO Expand the one line overview item to show some details
- }
@Override
public void authenticate(String username, String password) {
@@ -319,8 +384,101 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf
}
}
- // Used for getting Context when outside of a class extending Context
+ /**
+ * For retrieving the base application Context in classes that don't extend
+ * Android's Activity class
+ *
+ * @return Application Context as defined by <code>this</code> for Dashboard instance
+ */
public static Context getAppContext() {
return app;
}
+
+ @Override
+ public void updateState(final String state, final String logmessage, final int localizedResId) {
+ // Note: "states" are not organized anywhere...collected state strings:
+ // NOPROCESS,NONETWORK,BYTECOUNT,AUTH_FAILED + some parsing thing ( WAIT(?),AUTH,GET_CONFIG,ASSIGN_IP,CONNECTED(?) )
+ runOnUiThread(new Runnable() {
+
+ @Override
+ public void run() {
+ if (eipStatus != null) {
+ String prefix = getString(localizedResId) + ":";
+ if (state.equals("BYTECOUNT") || state.equals("NOPROCESS"))
+ prefix = "";
+ eipStatus.setText(prefix + logmessage);
+ }
+ }
+ });
+ }
+
+ /**
+ * Inner class for handling messages related to EIP status and control requests
+ *
+ * @author Sean Leonard <meanderingcode@aetherislands.net>
+ */
+ protected class EIPReceiver extends ResultReceiver {
+
+ Dashboard mDashboard;
+
+ protected EIPReceiver(Handler handler){
+ super(handler);
+ }
+
+ public void setReceiver(Dashboard receiver) {
+ mDashboard = receiver;
+ }
+
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ super.onReceiveResult(resultCode, resultData);
+
+ String request = resultData.getString(ConfigHelper.REQUEST_TAG);
+ mEipWait = true;
+ boolean checked = false;
+
+ if (request == EIP.ACTION_IS_EIP_RUNNING) {
+ switch (resultCode){
+ case RESULT_OK:
+ checked = true;
+ break;
+ case RESULT_CANCELED:
+ checked = false;
+ break;
+ }
+ } else if (request == EIP.ACTION_START_EIP) {
+ switch (resultCode){
+ case RESULT_OK:
+ checked = true;
+ break;
+ case RESULT_CANCELED:
+ checked = false;
+ break;
+ }
+ } else if (request == EIP.ACTION_STOP_EIP) {
+ switch (resultCode){
+ case RESULT_OK:
+ checked = false;
+ break;
+ case RESULT_CANCELED:
+ checked = true;
+ break;
+ }
+ } else if (request == EIP.EIP_NOTIFICATION) {
+ switch (resultCode){
+ case RESULT_OK:
+ checked = true;
+ break;
+ case RESULT_CANCELED:
+ checked = false;
+ break;
+ }
+ }
+
+ Switch eipS = ((Switch) mDashboard.findViewById(R.id.eipSwitch));
+ eipS.setChecked(checked);
+ eipS.setClickable(true);
+ mEipWait = false;
+ }
+ }
}
diff --git a/src/se/leap/leapclient/EIP.java b/src/se/leap/leapclient/EIP.java
new file mode 100644
index 00000000..426f4359
--- /dev/null
+++ b/src/se/leap/leapclient/EIP.java
@@ -0,0 +1,481 @@
+package se.leap.leapclient;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.Vector;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import se.leap.openvpn.ConfigParser;
+import se.leap.openvpn.ConfigParser.ConfigParseError;
+import se.leap.openvpn.LaunchVPN;
+import se.leap.openvpn.OpenVpnManagementThread;
+import se.leap.openvpn.OpenVpnService;
+import se.leap.openvpn.OpenVpnService.LocalBinder;
+import se.leap.openvpn.ProfileManager;
+import se.leap.openvpn.VpnProfile;
+
+import android.app.Activity;
+import android.app.IntentService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ResultReceiver;
+import android.util.Log;
+
+/**
+ * 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.
+ * Contains logic for parsing eip-service.json from the provider, configuring and selecting
+ * gateways, and controlling {@link .openvpn.OpenVpnService} connections.
+ *
+ * @author Sean Leonard <meanderingcode@aetherislands.net>
+ */
+public final class EIP extends IntentService {
+
+ public final static String ACTION_START_EIP = "se.leap.leapclient.START_EIP";
+ public final static String ACTION_STOP_EIP = "se.leap.leapclient.STOP_EIP";
+ public final static String ACTION_UPDATE_EIP_SERVICE = "se.leap.leapclient.UPDATE_EIP_SERVICE";
+ public final static String ACTION_IS_EIP_RUNNING = "se.leap.leapclient.IS_RUNNING";
+ public final static String EIP_NOTIFICATION = "EIP_NOTIFICATION";
+
+ private static Context context;
+ private static ResultReceiver mReceiver;
+ private static OpenVpnService mVpnService;
+ private static boolean mBound = false;
+ // Used to store actions to "resume" onServiceConnection
+ private static String mPending = null;
+
+ private static JSONObject eipDefinition = null;
+
+ private static OVPNGateway activeGateway = null;
+
+ public EIP(){
+ super("LEAPEIP");
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ context = getApplicationContext();
+
+ try {
+ eipDefinition = ConfigHelper.getJsonFromSharedPref(ConfigHelper.EIP_SERVICE_KEY);
+ } catch (JSONException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ this.retreiveVpnService();
+ }
+
+ @Override
+ public void onDestroy() {
+ unbindService(mVpnServiceConn);
+ mBound = false;
+
+ super.onDestroy();
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ String action = intent.getAction();
+ mReceiver = intent.getParcelableExtra(ConfigHelper.RECEIVER_TAG);
+
+ if ( action == ACTION_IS_EIP_RUNNING )
+ this.isRunning();
+ if ( action == ACTION_UPDATE_EIP_SERVICE )
+ this.updateEIPService();
+ else if ( action == ACTION_START_EIP )
+ this.startEIP();
+ else if ( action == ACTION_STOP_EIP )
+ this.stopEIP();
+ }
+
+ /**
+ * Sends an Intent to bind OpenVpnService.
+ * Used when OpenVpnService isn't bound but might be running.
+ */
+ private void retreiveVpnService() {
+ Intent bindIntent = new Intent(this,OpenVpnService.class);
+ bindIntent.setAction(OpenVpnService.RETRIEVE_SERVICE);
+ bindService(bindIntent, mVpnServiceConn, 0);
+ }
+
+ private static ServiceConnection mVpnServiceConn = new ServiceConnection() {
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ LocalBinder binder = (LocalBinder) service;
+ mVpnService = binder.getService();
+ mBound = true;
+
+ if (mReceiver != null && mPending != null) {
+
+ boolean running = mVpnService.isRunning();
+ int resultCode = Activity.RESULT_CANCELED;
+
+ if (mPending.equals(ACTION_IS_EIP_RUNNING))
+ resultCode = (running) ? Activity.RESULT_OK : Activity.RESULT_CANCELED;
+ if (mPending.equals(ACTION_START_EIP))
+ resultCode = (running) ? Activity.RESULT_OK : Activity.RESULT_CANCELED;
+ else if (mPending.equals(ACTION_STOP_EIP))
+ resultCode = (running) ? Activity.RESULT_CANCELED
+ : Activity.RESULT_OK;
+ Bundle resultData = new Bundle();
+ resultData.putString(ConfigHelper.REQUEST_TAG, EIP_NOTIFICATION);
+ mReceiver.send(resultCode, resultData);
+
+ mPending = null;
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mBound = false;
+
+ if (mReceiver != null){
+ Bundle resultData = new Bundle();
+ resultData.putString(ConfigHelper.REQUEST_TAG, EIP_NOTIFICATION);
+ mReceiver.send(Activity.RESULT_CANCELED, resultData);
+ }
+ }
+
+ };
+
+ /**
+ * Attempts to determine if OpenVpnService has an established VPN connection
+ * through the bound ServiceConnection. If there is no bound service, this
+ * method will attempt to bind a running OpenVpnService and send
+ * <code>Activity.RESULT_CANCELED</code> to the ResultReceiver that made the
+ * request.
+ * Note: If the request to bind OpenVpnService is successful, the ResultReceiver
+ * will be notified in {@link onServiceConnected()}
+ */
+ private void isRunning() {
+ Bundle resultData = new Bundle();
+ resultData.putString(ConfigHelper.REQUEST_TAG, ACTION_IS_EIP_RUNNING);
+ int resultCode = Activity.RESULT_CANCELED;
+ if (mBound) {
+ resultCode = (mVpnService.isRunning()) ? Activity.RESULT_OK : Activity.RESULT_CANCELED;
+ } else {
+ mPending = ACTION_IS_EIP_RUNNING;
+ this.retreiveVpnService();
+ }
+
+ if (mReceiver != null){
+ mReceiver.send(resultCode, resultData);
+ }
+ }
+
+ /**
+ * Initiates an EIP connection by selecting a gateway and preparing and sending an
+ * Intent to {@link se.leap.openvpn.LaunchVPN}
+ */
+ private void startEIP() {
+ if (activeGateway==null)
+ activeGateway = selectGateway();
+
+ Intent intent = new Intent(this,LaunchVPN.class);
+ intent.setAction(Intent.ACTION_MAIN);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(LaunchVPN.EXTRA_KEY, activeGateway.mVpnProfile.getUUID().toString() );
+ intent.putExtra(LaunchVPN.EXTRA_NAME, activeGateway.mVpnProfile.getName() );
+ intent.putExtra(ConfigHelper.RECEIVER_TAG, mReceiver);
+ startActivity(intent);
+ mPending = ACTION_START_EIP;
+ }
+
+ /**
+ * Disconnects the EIP connection gracefully through the bound service or forcefully
+ * if there is no bound service. Sends a message to the requesting ResultReceiver.
+ */
+ private void stopEIP() {
+ if (mBound)
+ mVpnService.onRevoke();
+ else
+ OpenVpnManagementThread.stopOpenVPN();
+
+ if (mReceiver != null){
+ Bundle resultData = new Bundle();
+ resultData.putString(ConfigHelper.REQUEST_TAG, ACTION_STOP_EIP);
+ mReceiver.send(Activity.RESULT_OK, resultData);
+ }
+ }
+
+ /**
+ * Loads eip-service.json from SharedPreferences and calls {@link updateGateways()}
+ * to parse gateway definitions.
+ * TODO Implement API call to refresh eip-service.json from the provider
+ */
+ private void updateEIPService() {
+ try {
+ eipDefinition = ConfigHelper.getJsonFromSharedPref(ConfigHelper.EIP_SERVICE_KEY);
+ } catch (JSONException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ updateGateways();
+ }
+
+ /**
+ * Choose a gateway to connect to based on timezone from system locale data
+ *
+ * @return The gateway to connect to
+ */
+ private OVPNGateway selectGateway() {
+ // TODO Implement gateway selection logic based on TZ or preferences
+ // TODO Implement search through gateways loaded from SharedPreferences
+ // TODO Remove String arg constructor in favor of findGatewayByName(String)
+ return new OVPNGateway("first");
+ }
+
+ /**
+ * Walk the list of gateways defined in eip-service.json and parse them into
+ * OVPNGateway objects.
+ * TODO Store the OVPNGateways (as Serializable) in SharedPreferences
+ */
+ private void updateGateways(){
+ JSONArray gatewaysDefined = null;
+
+ try {
+ gatewaysDefined = eipDefinition.getJSONArray("gateways");
+ } catch (JSONException e1) {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+
+ for ( int i=0 ; i < gatewaysDefined.length(); i++ ){
+
+ JSONObject gw = null;
+
+ try {
+ gw = gatewaysDefined.getJSONObject(i);
+ } catch (JSONException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ try {
+ if ( gw.getJSONObject("capabilities").getJSONArray("transport").toString().contains("openvpn") ){
+ new OVPNGateway(gw);
+ }
+ } catch (JSONException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * OVPNGateway provides objects defining gateways and their options and metadata.
+ * Each instance contains a VpnProfile for OpenVPN specific data and member
+ * variables describing capabilities and location
+ *
+ * @author Sean Leonard <meanderingcode@aetherislands.net>
+ */
+ private class OVPNGateway {
+
+ private String TAG = "OVPNGateway";
+
+ private VpnProfile mVpnProfile;
+ private JSONObject mGateway;
+ private HashMap<String,Vector<Vector<String>>> options = new HashMap<String, Vector<Vector<String>>>();
+
+
+ /**
+ * Attempts to retrieve a VpnProfile by name and build an OVPNGateway around it.
+ * FIXME This needs to become a findGatewayByName() method
+ *
+ * @param name The hostname of the gateway to inflate
+ */
+ private OVPNGateway(String name){
+ ProfileManager vpl = ProfileManager.getInstance(context);
+
+ try {
+
+ if ( name == "first" ) {
+ name = vpl.getProfiles().iterator().next().mName;
+ }
+
+ mVpnProfile = vpl.getProfileByName(name);
+
+ } catch (NoSuchElementException e) {
+ updateEIPService();
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Build a gateway object from a JSON OpenVPN gateway definition in eip-service.json
+ * and create a VpnProfile belonging to it.
+ *
+ * @param gateway The JSON OpenVPN gateway definition to parse
+ */
+ protected OVPNGateway(JSONObject gateway){
+
+ mGateway = gateway;
+
+ // Currently deletes VpnProfile for host, if there already is one, and builds new
+ ProfileManager vpl = ProfileManager.getInstance(context);
+ Collection<VpnProfile> profiles = vpl.getProfiles();
+ for (VpnProfile p : profiles){
+ try {
+ if ( p.mName.contains( gateway.getString("host") ) )
+ vpl.removeProfile(context, p);
+ } catch (JSONException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ this.parseOptions();
+ this.createVPNProfile();
+
+ setUniqueProfileName(vpl);
+ vpl.addProfile(mVpnProfile);
+ vpl.saveProfile(context, mVpnProfile);
+ vpl.saveProfileList(context);
+ }
+
+ /**
+ * Attempts to create a unique profile name from the hostname of the gateway
+ *
+ * @param profileManager
+ */
+ private void setUniqueProfileName(ProfileManager profileManager) {
+ int i=0;
+
+ String newname;
+ try {
+ newname = mGateway.getString("host");
+ while(profileManager.getProfileByName(newname)!=null) {
+ i++;
+ if(i==1)
+ newname = getString(R.string.converted_profile);
+ else
+ newname = getString(R.string.converted_profile_i,i);
+ }
+
+ mVpnProfile.mName=newname;
+ } catch (JSONException e) {
+ // TODO Auto-generated catch block
+ Log.v(TAG,"Couldn't read gateway name for profile creation!");
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * FIXME This method is really the outline of the refactoring needed in se.leap.openvpn.ConfigParser
+ */
+ private void parseOptions(){
+
+ // FIXME move these to a common API (& version) definition place, like ProviderAPI or ConfigHelper
+ String common_options = "openvpn_configuration";
+ String remote = "ip_address";
+ String ports = "ports";
+ String protos = "protocols";
+ String capabilities = "capabilities";
+
+ Vector<String> arg = new Vector<String>();
+ Vector<Vector<String>> args = new Vector<Vector<String>>();
+
+ try {
+ JSONObject def = (JSONObject) eipDefinition.get(common_options);
+ Iterator keys = def.keys();
+ Vector<Vector<String>> value = new Vector<Vector<String>>();
+ while ( keys.hasNext() ){
+ String key = keys.next().toString();
+
+ arg.add(key);
+ for ( String word : def.getString(key).split(" ") )
+ arg.add(word);
+ value.add( (Vector<String>) arg.clone() );
+ options.put(key, (Vector<Vector<String>>) value.clone());
+
+ value.clear();
+ arg.clear();
+ }
+ } catch (JSONException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ try {
+ arg.add("remote");
+ arg.add(mGateway.getString(remote));
+ } catch (JSONException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ args.add((Vector<String>) arg.clone());
+ options.put("remote", (Vector<Vector<String>>) args.clone() );
+ arg.clear();
+ args.clear();
+
+ JSONArray protocolsJSON = null;
+ arg.add("proto");
+ try {
+ protocolsJSON = mGateway.getJSONObject(capabilities).getJSONArray(protos);
+ } catch (JSONException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ Vector<String> protocols = new Vector<String>();
+ for ( int i=0; i<protocolsJSON.length(); i++ )
+ protocols.add(protocolsJSON.optString(i));
+ if ( protocols.contains("udp"))
+ arg.add("udp");
+ else if ( protocols.contains("tcp"))
+ arg.add("tcp");
+ args.add((Vector<String>) arg.clone());
+ options.put("proto", (Vector<Vector<String>>) args.clone());
+ arg.clear();
+ args.clear();
+
+
+ String port = null;
+ arg.add("port");
+ try {
+ port = mGateway.getJSONObject(capabilities).getJSONArray(ports).optString(0);
+ } catch (JSONException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ arg.add(port);
+ args.add((Vector<String>) arg.clone());
+ options.put("port", (Vector<Vector<String>>) args.clone());
+ args.clear();
+ arg.clear();
+ }
+
+ /**
+ * Create and attach the VpnProfile to our gateway object
+ */
+ protected void createVPNProfile(){
+ try {
+ ConfigParser cp = new ConfigParser();
+ cp.setDefinition(options);
+ VpnProfile vp = cp.convertProfile();
+ mVpnProfile = vp;
+ Log.v(TAG,"Created VPNProfile");
+ } catch (ConfigParseError e) {
+ // FIXME We didn't get a VpnProfile! Error handling! and log level
+ Log.v(TAG,"Error createing VPNProfile");
+ e.printStackTrace();
+ }
+ }
+ }
+
+}
diff --git a/src/se/leap/leapclient/Provider.java b/src/se/leap/leapclient/Provider.java
index 50bd2925..189c3a84 100644
--- a/src/se/leap/leapclient/Provider.java
+++ b/src/se/leap/leapclient/Provider.java
@@ -122,8 +122,9 @@ public final class Provider implements Serializable {
} catch (Exception e) {
// TODO: handle exception
}
- for (int i=0;i<API_EIP_TYPES.length;i++){
+ for (int i=0;i<API_EIP_TYPES.length+1;i++){
try {
+ // Walk the EIP types array looking for matches in provider's service definitions
if ( Arrays.asList(API_EIP_TYPES).contains( services.getString(i) ) )
return true;
} catch (JSONException e) {
diff --git a/src/se/leap/leapclient/ProviderAPI.java b/src/se/leap/leapclient/ProviderAPI.java
index 00d7d820..72cc9b62 100644
--- a/src/se/leap/leapclient/ProviderAPI.java
+++ b/src/se/leap/leapclient/ProviderAPI.java
@@ -584,8 +584,18 @@ public class ProviderAPI extends IntentService {
boolean danger_on = ConfigHelper.getBoolFromSharedPref(ConfigHelper.DANGER_ON);
String cert_string = getStringFromProvider(new_cert_string_url, danger_on);
- if(!cert_string.isEmpty()) {
- ConfigHelper.saveSharedPref(ConfigHelper.CERT_KEY, cert_string);
+ if(!cert_string.isEmpty()) {
+ // API returns concatenated cert & key. Split them for OpenVPN options
+ String certificate = null, key = null;
+ String[] certAndKey = cert_string.split("(?<=-\n)");
+ for (int i=0; i < certAndKey.length-1; i++){
+ if ( certAndKey[i].contains("KEY") )
+ key = certAndKey[i++] + certAndKey[i];
+ else if ( certAndKey[i].contains("CERTIFICATE") )
+ certificate = certAndKey[i++] + certAndKey[i];
+ }
+ ConfigHelper.saveSharedPref(ConfigHelper.CERT_KEY, certificate);
+ ConfigHelper.saveSharedPref(ConfigHelper.KEY_KEY, key);
return true;
} else {
return false;
diff --git a/src/se/leap/openvpn/ConfigParser.java b/src/se/leap/openvpn/ConfigParser.java
index f57bbae9..3d369fa6 100644
--- a/src/se/leap/openvpn/ConfigParser.java
+++ b/src/se/leap/openvpn/ConfigParser.java
@@ -47,6 +47,9 @@ public class ConfigParser {
options.get(optionname).add(args);
}
}
+ public void setDefinition(HashMap<String,Vector<Vector<String>>> args) {
+ options = args;
+ }
private void checkinlinefile(Vector<String> args, BufferedReader br) throws IOException, ConfigParseError {
String arg0 = args.get(0);
@@ -247,7 +250,8 @@ public class ConfigParser {
// Pull, client, tls-client
np.clearDefaults();
- if(options.containsKey("client") || options.containsKey("pull")) {
+ // XXX we are always client
+ if(/*options.containsKey("client") || options.containsKey("pull")*/ true) {
np.mUsePull=true;
options.remove("pull");
options.remove("client");
diff --git a/src/se/leap/openvpn/LaunchVPN.java b/src/se/leap/openvpn/LaunchVPN.java
index 3df2aacb..1df6be96 100644
--- a/src/se/leap/openvpn/LaunchVPN.java
+++ b/src/se/leap/openvpn/LaunchVPN.java
@@ -19,6 +19,9 @@ package se.leap.openvpn;
import java.io.IOException;
import java.util.Collection;
import java.util.Vector;
+
+import se.leap.leapclient.ConfigHelper;
+import se.leap.leapclient.EIP;
import se.leap.leapclient.R;
import android.app.Activity;
@@ -32,6 +35,7 @@ import android.content.SharedPreferences;
import android.net.VpnService;
import android.os.Bundle;
import android.os.Parcelable;
+import android.os.ResultReceiver;
import android.preference.PreferenceManager;
import android.text.InputType;
import android.text.method.PasswordTransformationMethod;
@@ -74,8 +78,10 @@ public class LaunchVPN extends ListActivity implements OnItemClickListener {
public static final String EXTRA_NAME = "se.leap.openvpn.shortcutProfileName";
public static final String EXTRA_HIDELOG = "se.leap.openvpn.showNoLogWindow";;
- private static final int START_VPN_PROFILE= 70;
+ public static final int START_VPN_PROFILE= 70;
+ // Dashboard, maybe more, want to know!
+ private ResultReceiver mReceiver;
private ProfileManager mPM;
private VpnProfile mSelectedProfile;
@@ -99,6 +105,9 @@ public class LaunchVPN extends ListActivity implements OnItemClickListener {
final Intent intent = getIntent();
final String action = intent.getAction();
+ // If something wants feedback, they sent us a Receiver
+ mReceiver = intent.getParcelableExtra(ConfigHelper.RECEIVER_TAG);
+
// If the intent is a request to create a shortcut, we'll do that and exit
@@ -266,14 +275,18 @@ public class LaunchVPN extends ListActivity implements OnItemClickListener {
askForPW(needpw);
} else {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
- boolean showlogwindow = prefs.getBoolean("showlogwindow", true);
+ boolean showlogwindow = prefs.getBoolean("showlogwindow", false);
if(!mhideLog && showlogwindow)
showLogWindow();
new startOpenVpnThread().start();
}
} else if (resultCode == Activity.RESULT_CANCELED) {
- // User does not want us to start, so we just vanish
+ // User does not want us to start, so we just vanish (well, now we tell our receiver, then vanish)
+ Bundle resultData = new Bundle();
+ // For now, nothing else is calling, so this "request" string is good enough
+ resultData.putString(ConfigHelper.REQUEST_TAG, EIP.ACTION_START_EIP);
+ mReceiver.send(RESULT_CANCELED, resultData);
finish();
}
}
@@ -357,6 +370,11 @@ public class LaunchVPN extends ListActivity implements OnItemClickListener {
@Override
public void run() {
VPNLaunchHelper.startOpenVpn(mSelectedProfile, getBaseContext());
+ // Tell whom-it-may-concern that we started VPN
+ Bundle resultData = new Bundle();
+ // For now, nothing else is calling, so this "request" string is good enough
+ resultData.putString(ConfigHelper.REQUEST_TAG, EIP.ACTION_START_EIP);
+ mReceiver.send(RESULT_OK, resultData);
finish();
}
diff --git a/src/se/leap/openvpn/OpenVpnService.java b/src/se/leap/openvpn/OpenVpnService.java
index c745ee3b..08a5d62e 100644
--- a/src/se/leap/openvpn/OpenVpnService.java
+++ b/src/se/leap/openvpn/OpenVpnService.java
@@ -20,6 +20,8 @@ import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Vector;
+
+import se.leap.leapclient.Dashboard;
import se.leap.leapclient.R;
import android.annotation.TargetApi;
@@ -35,7 +37,6 @@ import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.net.VpnService;
import android.os.Binder;
-import android.os.Handler;
import android.os.Handler.Callback;
import android.os.Build;
import android.os.IBinder;
@@ -45,6 +46,7 @@ import se.leap.openvpn.OpenVPN.StateListener;
public class OpenVpnService extends VpnService implements StateListener, Callback {
public static final String START_SERVICE = "se.leap.openvpn.START_SERVICE";
+ public static final String RETRIEVE_SERVICE = "se.leap.openvpn.RETRIEVE_SERVICE";
private Thread mProcessThread=null;
@@ -89,7 +91,7 @@ public class OpenVpnService extends VpnService implements StateListener, Callbac
@Override
public IBinder onBind(Intent intent) {
String action = intent.getAction();
- if( action !=null && action.equals(START_SERVICE))
+ if( action !=null && (action.equals(START_SERVICE) || action.equals(RETRIEVE_SERVICE)) )
return mBinder;
else
return super.onBind(intent);
@@ -173,10 +175,10 @@ public class OpenVpnService extends VpnService implements StateListener, Callbac
}
PendingIntent getLogPendingIntent() {
- // Let the configure Button show the Log
- Intent intent = new Intent(getBaseContext(),LogWindow.class);
+ // Let the configure Button show the Dashboard
+ Intent intent = new Intent(Dashboard.getAppContext(),Dashboard.class);
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
- PendingIntent startLW = PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
+ PendingIntent startLW = PendingIntent.getActivity(getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
return startLW;
@@ -223,7 +225,8 @@ public class OpenVpnService extends VpnService implements StateListener, Callbac
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
- if(intent != null && intent.getAction() !=null &&intent.getAction().equals(START_SERVICE))
+ if( intent != null && intent.getAction() !=null &&
+ (intent.getAction().equals(START_SERVICE) || intent.getAction().equals(RETRIEVE_SERVICE)) )
return START_NOT_STICKY;
@@ -235,7 +238,7 @@ public class OpenVpnService extends VpnService implements StateListener, Callbac
mProfile = ProfileManager.get(profileUUID);
- showNotification("Starting VPN " + mProfile.mName,"Starting VPN " + mProfile.mName, false,0);
+ //showNotification("Starting VPN " + mProfile.mName,"Starting VPN " + mProfile.mName, false,0);
OpenVPN.addStateListener(this);
@@ -466,6 +469,13 @@ public class OpenVpnService extends VpnService implements StateListener, Callbac
mLocalIPv6 = ipv6addr;
}
+ public boolean isRunning() {
+ if (mStarting == true || mProcessThread != null)
+ return true;
+ else
+ return false;
+ }
+
@Override
public void updateState(String state,String logmessage, int resid) {
// If the process is not running, ignore any state,
@@ -477,7 +487,7 @@ public class OpenVpnService extends VpnService implements StateListener, Callbac
if("BYTECOUNT".equals(state)) {
if(mDisplayBytecount) {
- showNotification(logmessage,null,true,mConnecttime);
+ //showNotification(logmessage,null,true,mConnecttime);
}
} else {
if("CONNECTED".equals(state)) {
@@ -491,7 +501,7 @@ public class OpenVpnService extends VpnService implements StateListener, Callbac
// This also mean we are no longer connected, ignore bytecount messages until next
// CONNECTED
String ticker = getString(resid);
- showNotification(getString(resid) +" " + logmessage,ticker,false,0);
+ //showNotification(getString(resid) +" " + logmessage,ticker,false,0);
}
}
diff --git a/src/se/leap/openvpn/VpnProfile.java b/src/se/leap/openvpn/VpnProfile.java
index 38ee3c83..2262f565 100644
--- a/src/se/leap/openvpn/VpnProfile.java
+++ b/src/se/leap/openvpn/VpnProfile.java
@@ -20,6 +20,8 @@ import java.util.Vector;
import org.spongycastle.util.io.pem.PemObject;
import org.spongycastle.util.io.pem.PemWriter;
+
+import se.leap.leapclient.ConfigHelper;
import se.leap.leapclient.R;
import android.content.Context;
@@ -62,7 +64,7 @@ public class VpnProfile implements Serializable{
// Public attributes, since I got mad with getter/setter
// set members to default values
private UUID mUuid;
- public int mAuthenticationType = TYPE_KEYSTORE ;
+ public int mAuthenticationType = TYPE_CERTIFICATES ;
public String mName;
public String mAlias;
public String mClientCertFilename;
@@ -236,13 +238,18 @@ public class VpnProfile implements Serializable{
case VpnProfile.TYPE_USERPASS_CERTIFICATES:
cfg+="auth-user-pass\n";
case VpnProfile.TYPE_CERTIFICATES:
- // Ca
+ /*// Ca
cfg+=insertFileData("ca",mCaFilename);
// Client Cert + Key
cfg+=insertFileData("key",mClientKeyFilename);
cfg+=insertFileData("cert",mClientCertFilename);
-
+*/
+ // FIXME This is all we need...The whole switch statement can go...
+ cfg+="<ca>\n"+ConfigHelper.getStringFromSharedPref(ConfigHelper.MAIN_CERT_KEY)+"\n</ca>\n";
+ cfg+="<key>\n"+ConfigHelper.getStringFromSharedPref(ConfigHelper.KEY_KEY)+"\n</key>\n";
+ cfg+="<cert>\n"+ConfigHelper.getStringFromSharedPref(ConfigHelper.CERT_KEY)+"\n</cert>\n";
+
break;
case VpnProfile.TYPE_USERPASS_PKCS12:
cfg+="auth-user-pass\n";
@@ -492,8 +499,8 @@ public class VpnProfile implements Serializable{
Intent intent = new Intent(context,OpenVpnService.class);
if(mAuthenticationType == VpnProfile.TYPE_KEYSTORE || mAuthenticationType == VpnProfile.TYPE_USERPASS_KEYSTORE) {
- if(!saveCertificates(context))
- return null;
+ /*if(!saveCertificates(context))
+ return null;*/
}
intent.putExtra(prefix + ".ARGV" , buildOpenvpnArgv(context.getCacheDir()));
@@ -597,10 +604,10 @@ public class VpnProfile implements Serializable{
//! Return an error if somethign is wrong
public int checkProfile(Context context) {
- if(mAuthenticationType==TYPE_KEYSTORE || mAuthenticationType==TYPE_USERPASS_KEYSTORE) {
+/* if(mAuthenticationType==TYPE_KEYSTORE || mAuthenticationType==TYPE_USERPASS_KEYSTORE) {
if(mAlias==null)
return R.string.no_keystore_cert_selected;
- }
+ }*/
if(!mUsePull) {
if(mIPv4Address == null || cidrToIPAndNetmask(mIPv4Address) == null)