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.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.content.SharedPreferences; import android.os.Bundle; import android.os.IBinder; import android.os.ResultReceiver; import android.util.Log; /** * @author Sean Leonard * */ 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; // Binder to OpenVpnService for comm ops private static OpenVpnService mVpnService; private static boolean mBound = false; // Used to store actions to "resume" onServiceConnection private static String mPending = null; // Represents our Provider's eip-service.json private static JSONObject eipDefinition = null; // Our active gateway private static OVPNGateway activeGateway = null; public EIP(){ super("LEAPEIP"); } @Override public void onCreate() { super.onCreate(); context = getApplicationContext(); // Inflate our eip-service.json data 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) { // Get our action from the Intent String action = intent.getAction(); // Get the ResultReceiver, if any 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(); } 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) { // XXX tell mReceiver!! mBound = false; if (mReceiver != null){ Bundle resultData = new Bundle(); resultData.putString(ConfigHelper.REQUEST_TAG, EIP_NOTIFICATION); mReceiver.send(Activity.RESULT_CANCELED, resultData); } } }; private void isRunning() { // TODO I don't like that whatever requested this never receives a response // if OpenVpnService has not been START_SERVICE, though the one place this is used that's okay if (mBound) { if (mReceiver != null){ Bundle resultData = new Bundle(); resultData.putString(ConfigHelper.REQUEST_TAG, ACTION_IS_EIP_RUNNING); int resultCode = (mVpnService.isRunning()) ? Activity.RESULT_OK : Activity.RESULT_CANCELED; mReceiver.send(resultCode, resultData); } } else { mPending = ACTION_IS_EIP_RUNNING; this.retreiveVpnService(); } } 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); // Let's give it 2s to get rolling... TODO there really should be a better way to do this, or it's not needed. // Read more code in .openvpn package try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } // Bind OpenVpnService for comm ops if (!mBound){ mPending = ACTION_START_EIP; this.retreiveVpnService(); } else { if (mReceiver != null) { Bundle resultData = new Bundle(); resultData.putString(ConfigHelper.REQUEST_TAG, ACTION_START_EIP); mReceiver.send(Activity.RESULT_OK, resultData); } } } private void stopEIP() { if (mBound){ mVpnService.onRevoke(); /*if (mReceiver != null){ Bundle resultData = new Bundle(); resultData.putString(ConfigHelper.REQUEST_TAG, ACTION_STOP_EIP); mReceiver.send(Activity.RESULT_OK, resultData); }*/ } else { // TODO If OpenVpnService isn't bound, does that really always mean it's not running? // If it's not running, bindService doesn't work w/o START_SERVICE action, so... /*mPending = ACTION_STOP_EIP; this.retreiveVpnService();*/ } // Remove this if above comes back if (mReceiver != null){ Bundle resultData = new Bundle(); resultData.putString(ConfigHelper.REQUEST_TAG, ACTION_STOP_EIP); mReceiver.send(Activity.RESULT_OK, resultData); } } private void updateEIPService() { // TODO this will also fetch new eip-service.json try { eipDefinition = ConfigHelper.getJsonFromSharedPref(ConfigHelper.EIP_SERVICE_KEY); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } updateGateways(); } private OVPNGateway selectGateway() { // TODO Logic, yay! return new OVPNGateway("first"); } private void updateGateways(){ JSONArray gatewaysDefined = null; // Get our list of gateways try { gatewaysDefined = eipDefinition.getJSONArray("gateways"); } catch (JSONException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } // Walk the list of gateways and inflate them to VPNProfiles 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") ){ // We have an openvpn gateway! // Now build VPNProfiles and save their UUIDs // TODO create multiple profiles for each gateway to allow trying e.g. different ports when connections don't complete new OVPNGateway(gw); } } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } private class OVPNGateway { // Log tag private String TAG = "OVPNGateway"; // The actual VPN Profile object private VpnProfile mVpnProfile; // Our gateway definition from eip-service.json private JSONObject gateway; // This holds our OpenVPN options for creating the VPNProfile // Options get put here in the form that se.leap.openvpn.ConfigParser wants TODO will be gone w/ rewrite private HashMap>> options = new HashMap>>(); // Constructor to load a gateway by name private OVPNGateway(String name){ ProfileManager vpl = ProfileManager.getInstance(context); try { // FIXME ha, this got funny..it will get smart once i'm further... if ( name == "first" ) { name = vpl.getProfiles().iterator().next().mName; } mVpnProfile = vpl.getProfileByName(name); } catch (NoSuchElementException e) { // The gateway we were looking for is not in ProfileList! updateEIPService(); // TODO prompt user to fix config error } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } // Constructor to create a gateway by definition protected OVPNGateway(JSONObject gw){ // TODO We're going to build 1 profile per gateway, but eventually several gateway = gw; // Delete VpnProfile for host, if there already is one // FIXME There is a better way to check settings and update them, instead of destroy/rebuild // Also, this allows one gateway per hostname entry, so that had better be true from the server! // TODO Will we define multiple gateways per host, for variable options? or change how .openvpn.VpnProfile works? ProfileManager vpl = ProfileManager.getInstance(context); Collection 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(); } } // Create options HashMap for se.leap.openvpn.ConfigParser this.parseOptions(); // Now create the VPNProfile this.createVPNProfile(); // Now let's save it in the .openvpn package way setUniqueProfileName(vpl); vpl.addProfile(mVpnProfile); vpl.saveProfile(context, mVpnProfile); vpl.saveProfileList(context); } private void setUniqueProfileName(ProfileManager vpl) { int i=0; String newname; try { newname = gateway.getString("host"); while(vpl.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 whole thing will get rewritten when we modify ConfigParser // in fact, don't even bother looking, except to debug private void parseOptions(){ // TODO we will want to rewrite se.leap.openvpn.ConfigParser soon to be targeted at our use // 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"; // FIXME Our gateway definition has keys that are not OpenVPN options... // We need a hard spec for the eip-service.json and better handling in this class // Then we can stop dumping all the "capabilities" key:values into our options for parsing // Put our common options in // FIXME quite ugly. We don't need the nested vectors, as we're not byte-reading file input, but we haven't rewritten the parser, yet Vector arg = new Vector(); Vector> args = new Vector>(); try { JSONObject def = (JSONObject) eipDefinition.get(common_options); Iterator keys = def.keys(); Vector> value = new Vector>(); while ( keys.hasNext() ){ String key = keys.next().toString(); arg.add(key); for ( String word : def.getString(key).split(" ") ) arg.add(word); value.add( (Vector) arg.clone() ); options.put(key, (Vector>) value.clone()); value.clear(); arg.clear(); } } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } // Now our gateway-specific options // to hold 'em the way they're wanted for parsing // remote:ip_address try { arg.add("remote"); arg.add(gateway.getString(remote)); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } args.add((Vector) arg.clone()); options.put("remote", (Vector>) args.clone() ); arg.clear(); args.clear(); // proto:udp||tcp JSONArray protocolsJSON = null; arg.add("proto"); try { protocolsJSON = gateway.getJSONObject(capabilities).getJSONArray(protos); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } Vector protocols = new Vector(); for ( int i=0; i) arg.clone()); options.put("proto", (Vector>) args.clone()); arg.clear(); args.clear(); // Now ports...picking one 'cause i say so'... TODO we should have multiple profiles?... String port = null; arg.add("port"); try { port = gateway.getJSONObject(capabilities).getJSONArray(ports).optString(0); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } arg.add(port); args.add((Vector) arg.clone()); options.put("port", (Vector>) args.clone()); args.clear(); arg.clear(); } protected void createVPNProfile(){ // TODO take data from eip-service.json for openvpn gateway definitions and create VPNProfile for each try { ConfigParser cp = new ConfigParser(); cp.setDefinition(options); VpnProfile vp = cp.convertProfile(); mVpnProfile = vp; Log.v(TAG,"Created VPNProfile"); } catch (ConfigParseError e) { // FIXME Being here is bad because we didn't get a VpnProfile! Log.v(TAG,"Error createing VPNProfile"); e.printStackTrace(); } } } }