diff options
16 files changed, 350 insertions, 288 deletions
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/EIP.java b/app/src/main/java/se/leap/bitmaskclient/EIP.java index a7a17e5f..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,6 +236,10 @@ 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 @@ -468,7 +480,6 @@ public final class EIP extends IntentService { } } - this.parseOptions(); this.createVPNProfile(); setUniqueProfileName(vpl); @@ -504,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/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/untranslatable.xml b/app/src/main/res/values/untranslatable.xml index de355720..cc39df3d 100644 --- a/app/src/main/res/values/untranslatable.xml +++ b/app/src/main/res/values/untranslatable.xml @@ -854,4 +854,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + </resources>
\ No newline at end of file diff --git a/ics-openvpn-stripped/main/src/main/java/de/blinkt/openvpn/LaunchVPN.java b/ics-openvpn-stripped/main/src/main/java/de/blinkt/openvpn/LaunchVPN.java index eead600c..16519418 100644 --- a/ics-openvpn-stripped/main/src/main/java/de/blinkt/openvpn/LaunchVPN.java +++ b/ics-openvpn-stripped/main/src/main/java/de/blinkt/openvpn/LaunchVPN.java @@ -108,28 +108,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/ics-openvpn-stripped/main/src/main/java/de/blinkt/openvpn/VpnProfile.java b/ics-openvpn-stripped/main/src/main/java/de/blinkt/openvpn/VpnProfile.java index 9eed03f1..d351610d 100644 --- a/ics-openvpn-stripped/main/src/main/java/de/blinkt/openvpn/VpnProfile.java +++ b/ics-openvpn-stripped/main/src/main/java/de/blinkt/openvpn/VpnProfile.java @@ -2,10 +2,6 @@ package de.blinkt.openvpn; 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; @@ -93,7 +89,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; @@ -279,13 +275,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/ics-openvpn-stripped/main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java b/ics-openvpn-stripped/main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java index 4fbbe165..378b6b92 100644 --- a/ics-openvpn-stripped/main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java +++ b/ics-openvpn-stripped/main/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/ics-openvpn-stripped/main/src/main/java/de/blinkt/openvpn/core/OpenVpnService.java b/ics-openvpn-stripped/main/src/main/java/de/blinkt/openvpn/core/OpenVpnService.java index b5bba5d4..43b27212 100644 --- a/ics-openvpn-stripped/main/src/main/java/de/blinkt/openvpn/core/OpenVpnService.java +++ b/ics-openvpn-stripped/main/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/ics-openvpn-stripped/main/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java b/ics-openvpn-stripped/main/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java index 1abcc54d..d96a66a0 100644 --- a/ics-openvpn-stripped/main/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java +++ b/ics-openvpn-stripped/main/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java @@ -5,33 +5,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; @@ -40,31 +46,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; @@ -423,8 +427,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) { @@ -490,10 +504,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); - - - } @@ -529,14 +539,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/ics-openvpn-stripped/main/src/main/res/menu/logmenu.xml b/ics-openvpn-stripped/main/src/main/res/menu/logmenu.xml index c498eefc..c8c9e815 100644 --- a/ics-openvpn-stripped/main/src/main/res/menu/logmenu.xml +++ b/ics-openvpn-stripped/main/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/ics-openvpn-stripped/main/src/main/res/values-de/strings.xml b/ics-openvpn-stripped/main/src/main/res/values-de/strings.xml index ef9fb9d6..eb6ef36e 100755 --- a/ics-openvpn-stripped/main/src/main/res/values-de/strings.xml +++ b/ics-openvpn-stripped/main/src/main/res/values-de/strings.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> |