diff options
author | Parménides GV <parmegv@sdf.org> | 2014-06-27 20:12:32 +0200 |
---|---|---|
committer | Parménides GV <parmegv@sdf.org> | 2014-06-27 20:12:32 +0200 |
commit | 6057466bc8b4475bf4564b9143c60753c90f9aaa (patch) | |
tree | d5b00e5e24781753dda142adec337c0a3826da4e /app/src/main | |
parent | 482c378c2ff3a37a76ed5788cf4eaef30a63d517 (diff) | |
parent | fccf31f212eeec368b84d86cfb3775c815fdaa2b (diff) |
Merge branch 'develop'0.5.3
Diffstat (limited to 'app/src/main')
34 files changed, 390 insertions, 245 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a2c2a82f..f73d59cb 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -17,8 +17,8 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="se.leap.bitmaskclient" - android:versionCode="79" - android:versionName="0.5.2" > + android:versionCode="84" + android:versionName="0.5.3" > <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> diff --git a/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java b/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java index f8487891..a39e780a 100644 --- a/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java +++ b/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java @@ -110,28 +110,28 @@ public class LaunchVPN extends Activity { } } - + + @Override protected void onActivityResult (int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if(requestCode==START_VPN_PROFILE) { - if(resultCode == Activity.RESULT_OK) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - boolean showlogwindow = prefs.getBoolean("showlogwindow", true); - - if(!mhideLog && showlogwindow) - showLogWindow(); - new startOpenVpnThread().start(); - } else if (resultCode == Activity.RESULT_CANCELED) { - // User does not want us to start, so we just vanish - VpnStatus.updateStateString("USER_VPN_PERMISSION_CANCELLED", "", R.string.state_user_vpn_permission_cancelled, - ConnectionStatus.LEVEL_NOTCONNECTED); - - finish(); - } + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + boolean showlogwindow = prefs.getBoolean("showlogwindow", true); + + if(!mhideLog && showlogwindow) + showLogWindow(); + new startOpenVpnThread().start(); + } else if (resultCode == Activity.RESULT_CANCELED) { + // User does not want us to start, so we just vanish + VpnStatus.updateStateString("USER_VPN_PERMISSION_CANCELLED", "", R.string.state_user_vpn_permission_cancelled, + ConnectionStatus.LEVEL_NOTCONNECTED); + + finish(); } } + void showLogWindow() { Intent startLW = new Intent(getBaseContext(),LogWindow.class); diff --git a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java index d21a085f..0166eb98 100644 --- a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java +++ b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java @@ -4,10 +4,6 @@ import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.R; -import se.leap.bitmaskclient.EIP; -import se.leap.bitmaskclient.Dashboard; -import se.leap.bitmaskclient.Provider; - import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -95,7 +91,7 @@ public class VpnProfile implements Serializable { // but needs to keep wrong name to guarante loading of old // profiles public transient boolean profileDleted = false; - public int mAuthenticationType = TYPE_CERTIFICATES; + public int mAuthenticationType = TYPE_KEYSTORE; public String mName; public String mAlias; public String mClientCertFilename; @@ -281,13 +277,14 @@ public class VpnProfile implements Serializable { switch (mAuthenticationType) { case VpnProfile.TYPE_USERPASS_CERTIFICATES: cfg += "auth-user-pass\n"; - case VpnProfile.TYPE_CERTIFICATES: - // FIXME This is all we need...The whole switch statement can go... - SharedPreferences preferences = context.getSharedPreferences(Dashboard.SHARED_PREFERENCES, context.MODE_PRIVATE); - cfg+="<ca>\n"+preferences.getString(Provider.CA_CERT, "")+"\n</ca>\n"; - cfg+="<key>\n"+preferences.getString(EIP.PRIVATE_KEY, "")+"\n</key>\n"; - cfg+="<cert>\n"+preferences.getString(EIP.CERTIFICATE, "")+"\n</cert>\n"; - + case VpnProfile.TYPE_CERTIFICATES: + // Ca + cfg += insertFileData("ca", mCaFilename); + + // Client Cert + Key + cfg += insertFileData("key", mClientKeyFilename); + cfg += insertFileData("cert", mClientCertFilename); + break; case VpnProfile.TYPE_USERPASS_PKCS12: cfg += "auth-user-pass\n"; diff --git a/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java b/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java index 4fbbe165..378b6b92 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java +++ b/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java @@ -26,14 +26,6 @@ public class ConfigParser { private boolean extraRemotesAsCustom=false; - /* - * TODO: We shouldn't be using this method. - * We need to figure out how to use just parseConfig, probably removing parseOptions. - */ - public void setDefinition(HashMap<String,Vector<Vector<String>>> args) { - options = args; - } - public void parseConfig(Reader reader) throws IOException, ConfigParseError { diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVpnService.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVpnService.java index b5bba5d4..43b27212 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVpnService.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVpnService.java @@ -34,7 +34,6 @@ import se.leap.bitmaskclient.BuildConfig; import se.leap.bitmaskclient.R; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.activities.DisconnectVPN; -import de.blinkt.openvpn.activities.LogWindow; import de.blinkt.openvpn.core.VpnStatus.ByteCountListener; import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus; import de.blinkt.openvpn.core.VpnStatus.StateListener; @@ -44,6 +43,8 @@ import static de.blinkt.openvpn.core.VpnStatus.ConnectionStatus.LEVEL_CONNECTED; import static de.blinkt.openvpn.core.VpnStatus.ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET; import static de.blinkt.openvpn.core.VpnStatus.ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT; +import se.leap.bitmaskclient.Dashboard; + public class OpenVpnService extends VpnService implements StateListener, Callback, ByteCountListener { public static final String START_SERVICE = "de.blinkt.openvpn.START_SERVICE"; public static final String START_SERVICE_STICKY = "de.blinkt.openvpn.START_SERVICE_STICKY"; @@ -72,13 +73,6 @@ public class OpenVpnService extends VpnService implements StateListener, Callbac private String mLastTunCfg; private String mRemoteGW; - //TODO We should know if this is running or not without this method - public boolean isRunning() { - if (mStarting == true || mProcessThread != null) - return true; - else - return false; - } // From: http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java public static String humanReadableByteCount(long bytes, boolean mbit) { if (mbit) @@ -243,7 +237,7 @@ public class OpenVpnService extends VpnService implements StateListener, Callbac PendingIntent getLogPendingIntent() { // Let the configure Button show the Log - Intent intent = new Intent(getBaseContext(), se.leap.bitmaskclient.Dashboard.class); + Intent intent = new Intent(getBaseContext(), Dashboard.class); intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); PendingIntent startLW = PendingIntent.getActivity(this, 0, intent, 0); intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java b/app/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java index 2f04d235..6e592121 100644 --- a/app/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java +++ b/app/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java @@ -7,33 +7,39 @@ import se.leap.bitmaskclient.R; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; -import android.app.*; -import android.content.*; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.ListFragment; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; import android.database.DataSetObserver; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; import android.os.Handler.Callback; -import android.os.IBinder; import android.os.Message; import android.text.SpannableString; import android.text.format.DateFormat; import android.text.style.ImageSpan; -import android.view.*; -import android.widget.*; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; import android.widget.AdapterView.OnItemLongClickListener; -import de.blinkt.openvpn.*; -import de.blinkt.openvpn.activities.DisconnectVPN; -import se.leap.bitmaskclient.Dashboard; -import de.blinkt.openvpn.core.OpenVPNManagement; -import de.blinkt.openvpn.core.VpnStatus; -import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus; -import de.blinkt.openvpn.core.VpnStatus.LogItem; -import de.blinkt.openvpn.core.VpnStatus.LogListener; -import de.blinkt.openvpn.core.VpnStatus.StateListener; -import de.blinkt.openvpn.core.OpenVpnService; -import de.blinkt.openvpn.core.OpenVpnService.LocalBinder; -import de.blinkt.openvpn.core.ProfileManager; +import android.widget.LinearLayout; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.RadioGroup; +import android.widget.SeekBar; +import android.widget.TextView; +import android.widget.Toast; + import org.jetbrains.annotations.Nullable; import java.text.SimpleDateFormat; @@ -42,31 +48,29 @@ import java.util.Date; import java.util.Locale; import java.util.Vector; +import de.blinkt.openvpn.LaunchVPN; +import se.leap.bitmaskclient.R; +import de.blinkt.openvpn.VpnProfile; +import de.blinkt.openvpn.activities.DisconnectVPN; +import de.blinkt.openvpn.core.OpenVPNManagement; +import de.blinkt.openvpn.core.OpenVpnService; +import de.blinkt.openvpn.core.ProfileManager; +import de.blinkt.openvpn.core.VpnStatus; +import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus; +import de.blinkt.openvpn.core.VpnStatus.LogItem; +import de.blinkt.openvpn.core.VpnStatus.LogListener; +import de.blinkt.openvpn.core.VpnStatus.StateListener; + import static de.blinkt.openvpn.core.OpenVpnService.humanReadableByteCount; +import se.leap.bitmaskclient.Dashboard; + public class LogFragment extends ListFragment implements StateListener, SeekBar.OnSeekBarChangeListener, RadioGroup.OnCheckedChangeListener, VpnStatus.ByteCountListener { private static final String LOGTIMEFORMAT = "logtimeformat"; private static final int START_VPN_CONFIG = 0; private static final String VERBOSITYLEVEL = "verbositylevel"; - protected OpenVpnService mService; - private ServiceConnection mConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName className, - IBinder service) { - // We've bound to LocalService, cast the IBinder and get LocalService instance - LocalBinder binder = (LocalBinder) service; - mService = binder.getService(); - } - - @Override - public void onServiceDisconnected(ComponentName arg0) { - mService =null; - } - - }; - private SeekBar mLogLevelSlider; private LinearLayout mOptionsLayout; private RadioGroup mTimeRadioGroup; @@ -425,8 +429,18 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar. Intent intent = new Intent(getActivity(),DisconnectVPN.class); startActivity(intent); return true; - } else if(item.getItemId()==R.id.send) { + } else if(item.getItemId()==R.id.send) { ladapter.shareLog(); + } else if(item.getItemId()==R.id.edit_vpn) { + VpnProfile lastConnectedprofile = ProfileManager.getLastConnectedVpn(); + + if(lastConnectedprofile!=null) { + Intent vprefintent = new Intent(getActivity(),Dashboard.class) + .putExtra(VpnProfile.EXTRA_PROFILEUUID,lastConnectedprofile.getUUIDString()); + startActivityForResult(vprefintent,START_VPN_CONFIG); + } else { + Toast.makeText(getActivity(), R.string.log_no_last_vpn, Toast.LENGTH_LONG).show(); + } } else if(item.getItemId() == R.id.toggle_time) { showHideOptionsPanel(); } else if(item.getItemId() == android.R.id.home) { @@ -492,10 +506,6 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar. Intent intent = new Intent(getActivity(), OpenVpnService.class); intent.setAction(OpenVpnService.START_SERVICE); - getActivity().bindService(intent, mConnection, Context.BIND_AUTO_CREATE); - - - } @@ -531,14 +541,13 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar. super.onActivityResult(requestCode, resultCode, data); } - @Override + + @Override public void onStop() { super.onStop(); VpnStatus.removeStateListener(this); VpnStatus.removeByteCountListener(this); - if(mService!=null) - getActivity().unbindService(mConnection); getActivity().getPreferences(0).edit().putInt(LOGTIMEFORMAT, ladapter.mTimeFormat) .putInt(VERBOSITYLEVEL, ladapter.mLogLevel).apply(); diff --git a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java index f2763d84..cb451b86 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java +++ b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java @@ -496,7 +496,7 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf case ProviderAPI.SRP_AUTHENTICATION_FAILED: eipStatus.setText(R.string.authentication_failed_message); break; case ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE: eipStatus.setText(R.string.authed_secured_status); break; case ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE: eipStatus.setText(R.string.incorrectly_downloaded_certificate_message); break; - case ProviderAPI.LOGOUT_SUCCESSFUL: eipStatus.setText(R.string.anonymous_secured_status); break; + case ProviderAPI.LOGOUT_SUCCESSFUL: eipStatus.setText(R.string.logged_out_message); break; case ProviderAPI.LOGOUT_FAILED: eipStatus.setText(R.string.log_out_failed_message); break; } @@ -507,9 +507,9 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf case ProviderAPI.SRP_AUTHENTICATION_SUCCESSFUL: eipStatus.setText(R.string.succesful_authentication_message); break; case ProviderAPI.SRP_AUTHENTICATION_FAILED: eipStatus.setText(R.string.authentication_failed_message); break; - case ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE: eipStatus.setText(R.string.future_authed_secured_status); break; + case ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE: break; case ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE: eipStatus.setText(R.string.incorrectly_downloaded_certificate_message); break; - case ProviderAPI.LOGOUT_SUCCESSFUL: eipStatus.setText(R.string.future_anonymous_secured_status); break; + case ProviderAPI.LOGOUT_SUCCESSFUL: eipStatus.setText(R.string.logged_out_message); break; case ProviderAPI.LOGOUT_FAILED: eipStatus.setText(R.string.log_out_failed_message); break; } } diff --git a/app/src/main/java/se/leap/bitmaskclient/EIP.java b/app/src/main/java/se/leap/bitmaskclient/EIP.java index 59faf93f..21a573fe 100644 --- a/app/src/main/java/se/leap/bitmaskclient/EIP.java +++ b/app/src/main/java/se/leap/bitmaskclient/EIP.java @@ -14,8 +14,10 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - package se.leap.bitmaskclient; +package se.leap.bitmaskclient; +import java.io.StringReader; +import java.io.IOException; import java.util.Calendar; import java.util.Collection; import java.util.HashMap; @@ -31,6 +33,9 @@ import org.json.JSONException; import org.json.JSONObject; import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.Dashboard; +import se.leap.bitmaskclient.Provider; + import de.blinkt.openvpn.activities.DisconnectVPN; import de.blinkt.openvpn.core.ConfigParser; import de.blinkt.openvpn.core.ConfigParser.ConfigParseError; @@ -40,12 +45,14 @@ import de.blinkt.openvpn.core.OpenVpnService; import de.blinkt.openvpn.core.OpenVpnService.LocalBinder; import de.blinkt.openvpn.core.ProfileManager; import de.blinkt.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.drm.DrmStore.Action; import android.os.Bundle; import android.os.IBinder; @@ -60,6 +67,7 @@ import android.util.Log; * 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 { @@ -142,7 +150,7 @@ public final class EIP extends IntentService { return bindService(bindIntent, mVpnServiceConn, BIND_AUTO_CREATE); } - private static ServiceConnection mVpnServiceConn = new ServiceConnection() { + private ServiceConnection mVpnServiceConn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { LocalBinder binder = (LocalBinder) service; @@ -151,7 +159,7 @@ public final class EIP extends IntentService { if (mReceiver != null && mPending != null) { - boolean running = mVpnService.isRunning(); + boolean running = isConnected(); int resultCode = Activity.RESULT_CANCELED; @@ -202,7 +210,7 @@ public final class EIP extends IntentService { Bundle resultData = new Bundle(); resultData.putString(REQUEST_TAG, ACTION_IS_EIP_RUNNING); int resultCode = Activity.RESULT_CANCELED; - boolean is_connected = getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).getString(STATUS, "").equalsIgnoreCase("LEVEL_CONNECTED"); + boolean is_connected = isConnected(); if (mBound) { resultCode = (is_connected) ? Activity.RESULT_OK : Activity.RESULT_CANCELED; @@ -228,14 +236,19 @@ public final class EIP extends IntentService { } } } + + private boolean isConnected() { + return getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).getString(STATUS, "").equalsIgnoreCase("LEVEL_CONNECTED"); + } /** * Initiates an EIP connection by selecting a gateway and preparing and sending an * Intent to {@link se.leap.openvpn.LaunchVPN} */ private void startEIP() { - activeGateway = selectGateway(); + activeGateway = selectGateway(); + if(activeGateway != null && activeGateway.mVpnProfile != null) { Intent intent = new Intent(this,LaunchVPN.class); intent.setAction(Intent.ACTION_MAIN); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); @@ -245,6 +258,7 @@ public final class EIP extends IntentService { intent.putExtra(RECEIVER_TAG, mReceiver); startActivity(intent); mPending = ACTION_START_EIP; + } } /** @@ -466,7 +480,6 @@ public final class EIP extends IntentService { } } - this.parseOptions(); this.createVPNProfile(); setUniqueProfileName(vpl); @@ -502,125 +515,152 @@ public final class EIP extends IntentService { } } - /** - * 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"; - String location_key = "location"; - String locations = "locations"; - - Vector<String> arg = new Vector<String>(); - Vector<Vector<String>> args = new Vector<Vector<String>>(); + /** + * Parses data from eip-service.json to a section of the openvpn config file + */ + private String configFromEipServiceDotJson() { + String parsed_configuration = ""; + + String common_options = "openvpn_configuration"; + String remote = "ip_address"; + String ports = "ports"; + String protos = "protocols"; + String capabilities = "capabilities"; + String location_key = "location"; + String locations = "locations"; + + 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(); + try { + JSONObject openvpn_configuration = eipDefinition.getJSONObject(common_options); + Iterator keys = openvpn_configuration.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(); - } + parsed_configuration += key + " "; + for ( String word : openvpn_configuration.getString(key).split(" ") ) + parsed_configuration += word + " "; + parsed_configuration += System.getProperty("line.separator"); + + } + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } - // We are always client, because the ifconfig will be received by a needed command - options.put("client", null); + parsed_configuration += "client" + System.getProperty("line.separator"); - 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(); + try { + JSONArray protocolsJSON = mGateway.getJSONObject(capabilities).getJSONArray(protos); + String remote_line = "remote"; + for ( int i=0; i<protocolsJSON.length(); i++ ) { + remote_line += " " + mGateway.getString(remote); + remote_line += " " + mGateway.getJSONObject(capabilities).getJSONArray(ports).optString(0); + remote_line += " " + protocolsJSON.optString(i); + if(remote_line.endsWith("udp")) + parsed_configuration = parsed_configuration.replaceFirst(System.getProperty("line.separator") + "remote", System.getProperty("line.separator") + remote_line + System.getProperty("line.separator") + "remote"); + else + parsed_configuration += remote_line; + remote_line = "remote"; + parsed_configuration += System.getProperty("line.separator"); + } + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + // try { + // arg.add(location_key); + // String locationText = ""; + // locationText = eipDefinition.getJSONObject(locations).getJSONObject(mGateway.getString(location_key)).getString("name"); + // arg.add(locationText); + // Log.d(TAG, "location = " + locationText); + // } catch (JSONException e) { + // // TODO Auto-generated catch block + // e.printStackTrace(); + // } + // args.add((Vector<String>) arg.clone()); + // options.put("location", (Vector<Vector<String>>) args.clone() ); - - // try { - // arg.add(location_key); - // String locationText = ""; - // locationText = eipDefinition.getJSONObject(locations).getJSONObject(mGateway.getString(location_key)).getString("name"); - // arg.add(locationText); - // Log.d(TAG, "location = " + locationText); - - // } catch (JSONException e) { - // // TODO Auto-generated catch block - // e.printStackTrace(); - // } - // args.add((Vector<String>) arg.clone()); - // options.put("location", (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(); - } + // arg.clear(); + // args.clear(); + return parsed_configuration; + } + + + private String caSecretFromSharedPreferences() { + String secret_lines = ""; + SharedPreferences preferences = context.getSharedPreferences(Dashboard.SHARED_PREFERENCES, context.MODE_PRIVATE); + + System.getProperty("line.separator"); + secret_lines += "<ca>"; + secret_lines += System.getProperty("line.separator"); + secret_lines += preferences.getString(Provider.CA_CERT, ""); + secret_lines += System.getProperty("line.separator"); + secret_lines += "</ca>"; + + return secret_lines; + } + + private String keySecretFromSharedPreferences() { + String secret_lines = ""; + SharedPreferences preferences = context.getSharedPreferences(Dashboard.SHARED_PREFERENCES, context.MODE_PRIVATE); + + secret_lines += System.getProperty("line.separator"); + secret_lines +="<key>"; + secret_lines += System.getProperty("line.separator"); + secret_lines += preferences.getString(EIP.PRIVATE_KEY, ""); + secret_lines += System.getProperty("line.separator"); + secret_lines += "</key>"; + secret_lines += System.getProperty("line.separator"); + + return secret_lines; + } + + private String certSecretFromSharedPreferences() { + String secret_lines = ""; + SharedPreferences preferences = context.getSharedPreferences(Dashboard.SHARED_PREFERENCES, context.MODE_PRIVATE); + + secret_lines += System.getProperty("line.separator"); + secret_lines +="<cert>"; + secret_lines += System.getProperty("line.separator"); + secret_lines += preferences.getString(EIP.CERTIFICATE, ""); + secret_lines += System.getProperty("line.separator"); + secret_lines += "</cert>"; + secret_lines += System.getProperty("line.separator"); + + return secret_lines; + } + /** * Create and attach the VpnProfile to our gateway object */ protected void createVPNProfile(){ try { ConfigParser cp = new ConfigParser(); - cp.setDefinition(options); + Log.d(TAG, configFromEipServiceDotJson()); + Log.d(TAG, caSecretFromSharedPreferences()); + Log.d(TAG, keySecretFromSharedPreferences()); + Log.d(TAG, certSecretFromSharedPreferences()); + cp.parseConfig(new StringReader(configFromEipServiceDotJson())); + cp.parseConfig(new StringReader(caSecretFromSharedPreferences())); + cp.parseConfig(new StringReader(keySecretFromSharedPreferences())); + cp.parseConfig(new StringReader(certSecretFromSharedPreferences())); VpnProfile vp = cp.convertProfile(); + //vp.mAuthenticationType=VpnProfile.TYPE_STATICKEYS; 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"); + Log.v(TAG,"Error creating VPNProfile"); + e.printStackTrace(); + } catch (IOException e) { + // FIXME We didn't get a VpnProfile! Error handling! and log level + Log.v(TAG,"Error creating VPNProfile"); e.printStackTrace(); } } diff --git a/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java b/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java index 446ba1d9..299d89a4 100644 --- a/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java @@ -221,7 +221,8 @@ public class EipServiceFragment extends Fragment implements StateListener, OnChe mEipStartPending = false; } else if ( level == ConnectionStatus.LEVEL_NONETWORK || level == ConnectionStatus.LEVEL_NOTCONNECTED || level == ConnectionStatus.LEVEL_AUTH_FAILED) { statusMessage = getString(R.string.eip_state_not_connected); - getActivity().findViewById(R.id.eipProgress).setVisibility(View.GONE); + if(getActivity() != null && getActivity().findViewById(R.id.eipProgress) != null) + getActivity().findViewById(R.id.eipProgress).setVisibility(View.GONE); mEipStartPending = false; switchState = false; } else if (level == ConnectionStatus.LEVEL_CONNECTING_SERVER_REPLIED) { diff --git a/app/src/main/java/se/leap/bitmaskclient/Provider.java b/app/src/main/java/se/leap/bitmaskclient/Provider.java index 216f4261..5326709f 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Provider.java +++ b/app/src/main/java/se/leap/bitmaskclient/Provider.java @@ -51,6 +51,7 @@ public final class Provider implements Serializable { SERVICE = "service", KEY = "provider", CA_CERT = "ca_cert", + CA_CERT_URI = "ca_cert_uri", NAME = "name", DESCRIPTION = "description", DOMAIN = "domain", diff --git a/app/src/main/res/drawable-hdpi/ic_stat_vpn.png b/app/src/main/res/drawable-hdpi/ic_stat_vpn.png Binary files differnew file mode 100644 index 00000000..c3547e85 --- /dev/null +++ b/app/src/main/res/drawable-hdpi/ic_stat_vpn.png diff --git a/app/src/main/res/drawable-hdpi/ic_stat_vpn_empty_halo.png b/app/src/main/res/drawable-hdpi/ic_stat_vpn_empty_halo.png Binary files differindex 7df5b670..25f82a95 100644 --- a/app/src/main/res/drawable-hdpi/ic_stat_vpn_empty_halo.png +++ b/app/src/main/res/drawable-hdpi/ic_stat_vpn_empty_halo.png diff --git a/app/src/main/res/drawable-hdpi/ic_stat_vpn_offline.png b/app/src/main/res/drawable-hdpi/ic_stat_vpn_offline.png Binary files differindex 8aa48803..dfb962b9 100644 --- a/app/src/main/res/drawable-hdpi/ic_stat_vpn_offline.png +++ b/app/src/main/res/drawable-hdpi/ic_stat_vpn_offline.png diff --git a/app/src/main/res/drawable-hdpi/ic_stat_vpn_outline.png b/app/src/main/res/drawable-hdpi/ic_stat_vpn_outline.png Binary files differindex b5583d99..96d8d34d 100644..120000 --- a/app/src/main/res/drawable-hdpi/ic_stat_vpn_outline.png +++ b/app/src/main/res/drawable-hdpi/ic_stat_vpn_outline.png diff --git a/app/src/main/res/drawable-hdpi/ic_vpn_disconnected.png b/app/src/main/res/drawable-hdpi/ic_vpn_disconnected.png Binary files differdeleted file mode 100644 index dfb962b9..00000000 --- a/app/src/main/res/drawable-hdpi/ic_vpn_disconnected.png +++ /dev/null diff --git a/app/src/main/res/drawable-ldpi/ic_stat_vpn.png b/app/src/main/res/drawable-ldpi/ic_stat_vpn.png Binary files differindex f973015c..65fc6db7 100644 --- a/app/src/main/res/drawable-ldpi/ic_stat_vpn.png +++ b/app/src/main/res/drawable-ldpi/ic_stat_vpn.png diff --git a/app/src/main/res/drawable-ldpi/ic_stat_vpn_empty_halo.png b/app/src/main/res/drawable-ldpi/ic_stat_vpn_empty_halo.png Binary files differnew file mode 100644 index 00000000..2df0a9bd --- /dev/null +++ b/app/src/main/res/drawable-ldpi/ic_stat_vpn_empty_halo.png diff --git a/app/src/main/res/drawable-ldpi/ic_stat_vpn_offline.png b/app/src/main/res/drawable-ldpi/ic_stat_vpn_offline.png Binary files differnew file mode 100644 index 00000000..22f3497e --- /dev/null +++ b/app/src/main/res/drawable-ldpi/ic_stat_vpn_offline.png diff --git a/app/src/main/res/drawable-ldpi/ic_stat_vpn_outline.png b/app/src/main/res/drawable-ldpi/ic_stat_vpn_outline.png new file mode 120000 index 00000000..482dafd3 --- /dev/null +++ b/app/src/main/res/drawable-ldpi/ic_stat_vpn_outline.png @@ -0,0 +1 @@ +./ic_stat_vpn_offline.png
\ No newline at end of file diff --git a/app/src/main/res/drawable-mdpi/ic_stat_vpn.png b/app/src/main/res/drawable-mdpi/ic_stat_vpn.png Binary files differnew file mode 100644 index 00000000..7e167f84 --- /dev/null +++ b/app/src/main/res/drawable-mdpi/ic_stat_vpn.png diff --git a/app/src/main/res/drawable-mdpi/ic_stat_vpn_empty_halo.png b/app/src/main/res/drawable-mdpi/ic_stat_vpn_empty_halo.png Binary files differindex fc039a82..a658d9e9 100644 --- a/app/src/main/res/drawable-mdpi/ic_stat_vpn_empty_halo.png +++ b/app/src/main/res/drawable-mdpi/ic_stat_vpn_empty_halo.png diff --git a/app/src/main/res/drawable-mdpi/ic_stat_vpn_offline.png b/app/src/main/res/drawable-mdpi/ic_stat_vpn_offline.png Binary files differindex f31387a4..f8b02bfb 100644 --- a/app/src/main/res/drawable-mdpi/ic_stat_vpn_offline.png +++ b/app/src/main/res/drawable-mdpi/ic_stat_vpn_offline.png diff --git a/app/src/main/res/drawable-mdpi/ic_stat_vpn_outline.png b/app/src/main/res/drawable-mdpi/ic_stat_vpn_outline.png Binary files differindex 052aef9d..96d8d34d 100644..120000 --- a/app/src/main/res/drawable-mdpi/ic_stat_vpn_outline.png +++ b/app/src/main/res/drawable-mdpi/ic_stat_vpn_outline.png diff --git a/app/src/main/res/drawable-xhdpi/ic_stat_vpn.png b/app/src/main/res/drawable-xhdpi/ic_stat_vpn.png Binary files differnew file mode 100644 index 00000000..1f46be2c --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/ic_stat_vpn.png diff --git a/app/src/main/res/drawable-xhdpi/ic_stat_vpn_empty_halo.png b/app/src/main/res/drawable-xhdpi/ic_stat_vpn_empty_halo.png Binary files differindex 2f61e890..f4f28ef7 100644 --- a/app/src/main/res/drawable-xhdpi/ic_stat_vpn_empty_halo.png +++ b/app/src/main/res/drawable-xhdpi/ic_stat_vpn_empty_halo.png diff --git a/app/src/main/res/drawable-xhdpi/ic_stat_vpn_offline.png b/app/src/main/res/drawable-xhdpi/ic_stat_vpn_offline.png Binary files differindex e9411518..7f44c46f 100644 --- a/app/src/main/res/drawable-xhdpi/ic_stat_vpn_offline.png +++ b/app/src/main/res/drawable-xhdpi/ic_stat_vpn_offline.png diff --git a/app/src/main/res/drawable-xhdpi/ic_stat_vpn_outline.png b/app/src/main/res/drawable-xhdpi/ic_stat_vpn_outline.png Binary files differindex 5d27240d..96d8d34d 100644..120000 --- a/app/src/main/res/drawable-xhdpi/ic_stat_vpn_outline.png +++ b/app/src/main/res/drawable-xhdpi/ic_stat_vpn_outline.png diff --git a/app/src/main/res/layout/about.xml b/app/src/main/res/layout/about.xml index 4b3f16e0..ccb1ea26 100644 --- a/app/src/main/res/layout/about.xml +++ b/app/src/main/res/layout/about.xml @@ -37,6 +37,12 @@ android:autoLink="all" android:text="@string/repository_url_text" /> + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:autoLink="all" + android:text="@string/leap_tracker" /> + <Space android:layout_width="match_parent" android:layout_height="10sp" /> diff --git a/app/src/main/res/layout/client_dashboard.xml b/app/src/main/res/layout/client_dashboard.xml index a5387efd..f33ac285 100644 --- a/app/src/main/res/layout/client_dashboard.xml +++ b/app/src/main/res/layout/client_dashboard.xml @@ -10,44 +10,28 @@ android:layout_width="match_parent" android:layout_height="40dp" android:background="?android:attr/selectableItemBackground" > - - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:gravity="center_vertical" - android:orientation="vertical" - android:paddingLeft="10dp" > - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:ellipsize="marquee" - android:fadingEdge="horizontal" - android:singleLine="true" - android:text="@string/provider_label" - android:textAppearance="?android:attr/textAppearanceMedium" - android:textSize="24sp" /> - - </LinearLayout> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="match_parent" - android:gravity="center_vertical" - android:orientation="vertical" - android:paddingLeft="15dp" > - - <TextView - android:id="@+id/providerName" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:ellipsize="marquee" - android:fadingEdge="horizontal" - android:singleLine="true" - android:text="@string/provider_label_none" - android:textAppearance="?android:attr/textAppearanceMedium" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="5dp" + android:ellipsize="marquee" + android:fadingEdge="horizontal" + android:singleLine="true" + android:text="@string/provider_label" + android:textAppearance="?android:attr/textAppearanceMedium" /> + + <TextView + android:id="@+id/providerName" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="10dp" + android:ellipsize="marquee" + android:fadingEdge="horizontal" + android:singleLine="true" + android:text="@string/provider_label_none" + android:textAppearance="?android:attr/textAppearanceMedium" /> - </LinearLayout> </LinearLayout> <View diff --git a/app/src/main/res/menu/logmenu.xml b/app/src/main/res/menu/logmenu.xml index c498eefc..c8c9e815 100644 --- a/app/src/main/res/menu/logmenu.xml +++ b/app/src/main/res/menu/logmenu.xml @@ -33,6 +33,7 @@ android:alphabeticShortcut="e" android:icon="@android:drawable/ic_menu_edit" android:showAsAction="withText|ifRoom" - android:title="@string/edit_vpn"/> + android:title="@string/edit_vpn" + android:visible="false"/> -</menu>
\ No newline at end of file +</menu> diff --git a/app/src/main/res/values-de/strings-icsopenvpn.xml b/app/src/main/res/values-de/strings-icsopenvpn.xml index 9bf58685..bf115be5 100755 --- a/app/src/main/res/values-de/strings-icsopenvpn.xml +++ b/app/src/main/res/values-de/strings-icsopenvpn.xml @@ -62,7 +62,7 @@ <string name="remove_vpn">VPN löschen</string> <string name="check_remote_tlscert">Überprüfe, ob der Server ein Zertifikat mit TLS-Servererweiterungen verwendet (--remote-cert-tls server)</string> <string name="check_remote_tlscert_title">TLS-Serverzertifikat erwarten</string> - <string name="remote_tlscn_check_summary">Server Zertifikatssubjekt überprüfen</string> + <string name="remote_tlscn_check_summary">Server Zertifikatssubjekt DN überprüfen</string> <string name="remote_tlscn_check_title">Zertifikat Namen überprüfen</string> <string name="enter_tlscn_dialog">Spezifizieren Sie die Methode mit welcher der DN des Serverzertifikates (z. B. C=DE, L=Paderborn, OU=Avian IP-Carrier, CN=openvpn.blinkt.de) überprüft wird.\n\nSie können den vollständigen DN oder den RDN (openvpn.blinkt.de im Beispiel) oder ein RDN-Präfix angeben.\n\nDer RDN Präfix \"Server\" erlaubt z.B. \"Server-1\" und \"Server-2\" \n\nWenn Sie das Eingabefeld leer lassen, wird der RDN gegen den Servernamen geprüft.\n\n Für weitere Details sehen Sie die Manpage von OpenVPN 2.3.1+ unter —verify-x509-name</string> <string name="enter_tlscn_title">Serverzertifikat Subject</string> @@ -84,7 +84,7 @@ <string name="default_route_summary">Leitet allen Internet Verkehr über das VPN</string> <string name="use_default_title">Benutze Default Route</string> <string name="custom_route_message">Benutze eigene Routen. Geben Sie Zielnetzwerk im CIDR Format an. Z.b. \"10.0.0.0/8 2002::/16\" würde die Netzwerke 10.0.0.0/8 und 2002::/16 über das VPN routen.</string> - <string name="custom_route_message_excluded">Netze, die nicht über das VPN weitergeleitet werden sollen. Nutzt die gleiche Syntax wie die eigenen Routen.</string> + <string name="custom_route_message_excluded">Netze, die nicht über das VPN geleitet werden sollen. Nutzt die gleiche Syntax wie die eigenen Routen.</string> <string name="custom_routes_title">Eigene Routen</string> <string name="custom_routes_title_excluded">Ausgeschlossene Netze</string> <string name="log_verbosity_level">Log Detail Level</string> @@ -312,7 +312,7 @@ <string name="unhandled_exception_context">%3$s: %1$s\n\n%2$s</string> <string name="faq_system_dialog_xposed">Wenn Sie ihr Gerät gerootet haben können Sie das <a href=\"http://xposed.info/\">Xposed Framework</a> und das <a href=\"http://repo.xposed.info/module/de.blinkt.vpndialogxposed\">VPN Dialog confirm Modul</a> auf eigene Gefahr installieren.</string> <string name="full_licenses">Komplette Lizenzen</string> - <string name="blocklocal_summary">Netze, die direkt über ein lokales Interfaces erreicht werden können werden nicht über das VPN gerottet. Deaktivieren dieser Option leitet allen Verkehr, der für lokale Netzwerke bestimmt ist, über das VPN.</string> + <string name="blocklocal_summary">Netze, die direkt über ein lokales Interfaces erreicht werden können werden nicht über das VPN geroutet. Deaktivieren dieser Option leitet allen Verkehr, der für lokale Netzwerke bestimmt ist, über das VPN.</string> <string name="blocklocal_title">VPN für lokale Netzwerke umgehen</string> <string name="userpw_file">Datei mit Benutzername und Passwort</string> <string name="imported_from_file">[Importiert aus %s]</string> diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 6a9cce29..ec8c21ff 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -6,4 +6,6 @@ <string name="routes_info6">Rutas IPv6: %s</string> <string name="error_empty_username">El nombre de usuario no debe estar vacío.</string> <string name="cert_from_keystore">Conseguido el certificado de \'%s\' de almacén de claves</string> + <string name="repository_url_text">Código fuente disponible en https://github.com/leapcode/bitmask_android/</string> + <string name="leap_tracker">Tracker disponible en https://leap.se/code</string> </resources> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d7114b73..c928f001 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2,7 +2,8 @@ <resources> <string name="retry">Retry</string> - <string name="repository_url_text">Source code and issue tracker available at https://github.com/leapcode/bitmask_android/</string> + <string name="repository_url_text">Source code available at https://github.com/leapcode/bitmask_android/</string> + <string name="leap_tracker">Tracker available at https://leap.se/code</string> <string name="translation_project_text">Translations welcome and appreciated. See our Transifex project at https://www.transifex.com/projects/p/bitmask-android/</string> <string name="switch_provider_menu_option">Switch provider</string> <string name="info">info</string> @@ -16,9 +17,7 @@ <string name="provider_label_none">No provider configured</string> <string name="eip_settings_button_description">Access EIP connection settings</string> <string name="status_unknown">Status unknown.</string> - <string name="future_anonymous_secured_status">Connection will be secure using an anonymous certificate.</string> <string name="anonymous_secured_status">Connection secure using an anonymous certificate.</string> - <string name="future_authed_secured_status">Connection will be secure using your own certificate.</string> <string name="authed_secured_status">Connection secure using your own certificate.</string> <string name="eip_service_label">Encrypted Internet</string> <string name="title_activity_configuration_wizard">Select a service provider</string> @@ -52,11 +51,13 @@ <string name="server_unreachable_message">Server is unreachable, please try again.</string> <string name="malformed_url">It doesn\'t seem to be a Bitmask provider.</string> <string name="certificate_error">This is not a trusted Bitmask provider.</string> + <string name="service_is_down_error">Service is down.</string> <string name="configuring_provider">Configuring provider</string> <string name="incorrectly_downloaded_certificate_message">Your anon cert was not downloaded</string> <string name="authenticating_message">Logging in</string> <string name="signingup_message">Signing up</string> <string name="logout_message">Logging out from this session.</string> + <string name="logged_out_message">Logged out.</string> <string name="log_out_failed_message">Didn\'t logged out.</string> <string name="succesful_authentication_message">Authentication succeeded.</string> <string name="authentication_failed_message">Authentication failed.</string> diff --git a/app/src/main/res/values/untranslatable.xml b/app/src/main/res/values/untranslatable.xml index 50e598ac..f956b6bd 100644 --- a/app/src/main/res/values/untranslatable.xml +++ b/app/src/main/res/values/untranslatable.xml @@ -3,7 +3,7 @@ <string name="app" translatable="false">Bitmask</string> <string name="app_name" translatable="false">Bitmask</string> - <string name="copyright_leapgui" translatable="false">Copyright 2012\nLEAP Encryption Access Project <info@leap.se></string> + <string name="copyright_leapgui" translatable="false">Copyright 2012-2014\nLEAP Encryption Access Project <info@leap.se></string> <string name="opevpn_copyright" translatable="false">Copyright © 2002–2010 OpenVPN Technologies, Inc. <sales@openvpn.net>\n "OpenVPN" is a trademark of OpenVPN Technologies, Inc.</string> @@ -777,4 +777,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + </resources>
\ No newline at end of file |