From b79fd315dd89e7a9f648c6a500b41ce7d9970081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parm=C3=A9nides=20GV?= Date: Wed, 14 May 2014 20:05:30 +0200 Subject: Copy all java files from ics-openvpn. imports from se.leap.bitmaskclient java files have also been updated. WARNING: compiling errors for de.blinkt.openvpn.R, aidl.de.blinkt.openvpn. --- .../main/java/de/blinkt/openvpn/FileProvider.java | 140 +++ app/src/main/java/de/blinkt/openvpn/LaunchVPN.java | 295 +++++++ .../java/de/blinkt/openvpn/OnBootReceiver.java | 34 + .../main/java/de/blinkt/openvpn/VpnProfile.java | 942 +++++++++++++++++++++ .../blinkt/openvpn/activities/ConfigConverter.java | 639 ++++++++++++++ .../blinkt/openvpn/activities/CreateShortcuts.java | 154 ++++ .../blinkt/openvpn/activities/DisconnectVPN.java | 82 ++ .../de/blinkt/openvpn/activities/FileSelect.java | 220 +++++ .../de/blinkt/openvpn/activities/LogWindow.java | 32 + .../de/blinkt/openvpn/activities/MainActivity.java | 103 +++ .../blinkt/openvpn/activities/VPNPreferences.java | 165 ++++ .../java/de/blinkt/openvpn/api/APIVpnProfile.java | 51 ++ .../java/de/blinkt/openvpn/api/ConfirmDialog.java | 126 +++ .../de/blinkt/openvpn/api/ExternalAppDatabase.java | 57 ++ .../blinkt/openvpn/api/ExternalOpenVPNService.java | 317 +++++++ .../openvpn/api/SecurityRemoteException.java | 12 + .../main/java/de/blinkt/openvpn/core/CIDRIP.java | 70 ++ .../java/de/blinkt/openvpn/core/ConfigParser.java | 767 +++++++++++++++++ .../blinkt/openvpn/core/DeviceStateReceiver.java | 243 ++++++ .../blinkt/openvpn/core/ICSOpenVPNApplication.java | 14 + .../java/de/blinkt/openvpn/core/NativeUtils.java | 13 + .../java/de/blinkt/openvpn/core/NetworkSpace.java | 300 +++++++ .../de/blinkt/openvpn/core/OpenVPNManagement.java | 24 + .../java/de/blinkt/openvpn/core/OpenVPNThread.java | 174 ++++ .../openvpn/core/OpenVpnManagementThread.java | 598 +++++++++++++ .../de/blinkt/openvpn/core/OpenVpnService.java | 757 +++++++++++++++++ .../java/de/blinkt/openvpn/core/PRNGFixes.java | 334 ++++++++ .../de/blinkt/openvpn/core/ProfileManager.java | 222 +++++ .../de/blinkt/openvpn/core/ProxyDetection.java | 55 ++ .../de/blinkt/openvpn/core/VPNLaunchHelper.java | 77 ++ .../java/de/blinkt/openvpn/core/VpnStatus.java | 540 ++++++++++++ .../java/de/blinkt/openvpn/core/X509Utils.java | 155 ++++ .../de/blinkt/openvpn/fragments/AboutFragment.java | 297 +++++++ .../de/blinkt/openvpn/fragments/FaqFragment.java | 36 + .../openvpn/fragments/FileSelectionFragment.java | 256 ++++++ .../de/blinkt/openvpn/fragments/InlineFileTab.java | 66 ++ .../de/blinkt/openvpn/fragments/LogFragment.java | 670 +++++++++++++++ .../fragments/OpenVpnPreferencesFragment.java | 48 ++ .../blinkt/openvpn/fragments/SendDumpFragment.java | 94 ++ .../openvpn/fragments/Settings_Authentication.java | 214 +++++ .../blinkt/openvpn/fragments/Settings_Basic.java | 360 ++++++++ .../de/blinkt/openvpn/fragments/Settings_IP.java | 130 +++ .../blinkt/openvpn/fragments/Settings_Obscure.java | 93 ++ .../blinkt/openvpn/fragments/Settings_Routing.java | 88 ++ .../openvpn/fragments/ShowConfigFragment.java | 89 ++ .../java/de/blinkt/openvpn/fragments/Utils.java | 221 +++++ .../blinkt/openvpn/fragments/VPNProfileList.java | 346 ++++++++ .../de/blinkt/openvpn/views/FileSelectLayout.java | 158 ++++ .../blinkt/openvpn/views/RemoteCNPreference.java | 141 +++ .../java/de/blinkt/openvpn/views/SeekBarTicks.java | 69 ++ 50 files changed, 11088 insertions(+) create mode 100644 app/src/main/java/de/blinkt/openvpn/FileProvider.java create mode 100644 app/src/main/java/de/blinkt/openvpn/LaunchVPN.java create mode 100644 app/src/main/java/de/blinkt/openvpn/OnBootReceiver.java create mode 100644 app/src/main/java/de/blinkt/openvpn/VpnProfile.java create mode 100644 app/src/main/java/de/blinkt/openvpn/activities/ConfigConverter.java create mode 100644 app/src/main/java/de/blinkt/openvpn/activities/CreateShortcuts.java create mode 100644 app/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java create mode 100644 app/src/main/java/de/blinkt/openvpn/activities/FileSelect.java create mode 100644 app/src/main/java/de/blinkt/openvpn/activities/LogWindow.java create mode 100644 app/src/main/java/de/blinkt/openvpn/activities/MainActivity.java create mode 100644 app/src/main/java/de/blinkt/openvpn/activities/VPNPreferences.java create mode 100644 app/src/main/java/de/blinkt/openvpn/api/APIVpnProfile.java create mode 100644 app/src/main/java/de/blinkt/openvpn/api/ConfirmDialog.java create mode 100644 app/src/main/java/de/blinkt/openvpn/api/ExternalAppDatabase.java create mode 100644 app/src/main/java/de/blinkt/openvpn/api/ExternalOpenVPNService.java create mode 100644 app/src/main/java/de/blinkt/openvpn/api/SecurityRemoteException.java create mode 100644 app/src/main/java/de/blinkt/openvpn/core/CIDRIP.java create mode 100644 app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java create mode 100644 app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java create mode 100644 app/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java create mode 100644 app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java create mode 100644 app/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java create mode 100644 app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java create mode 100644 app/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java create mode 100644 app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java create mode 100644 app/src/main/java/de/blinkt/openvpn/core/OpenVpnService.java create mode 100644 app/src/main/java/de/blinkt/openvpn/core/PRNGFixes.java create mode 100644 app/src/main/java/de/blinkt/openvpn/core/ProfileManager.java create mode 100644 app/src/main/java/de/blinkt/openvpn/core/ProxyDetection.java create mode 100644 app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java create mode 100644 app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java create mode 100644 app/src/main/java/de/blinkt/openvpn/core/X509Utils.java create mode 100644 app/src/main/java/de/blinkt/openvpn/fragments/AboutFragment.java create mode 100644 app/src/main/java/de/blinkt/openvpn/fragments/FaqFragment.java create mode 100644 app/src/main/java/de/blinkt/openvpn/fragments/FileSelectionFragment.java create mode 100644 app/src/main/java/de/blinkt/openvpn/fragments/InlineFileTab.java create mode 100644 app/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java create mode 100644 app/src/main/java/de/blinkt/openvpn/fragments/OpenVpnPreferencesFragment.java create mode 100644 app/src/main/java/de/blinkt/openvpn/fragments/SendDumpFragment.java create mode 100644 app/src/main/java/de/blinkt/openvpn/fragments/Settings_Authentication.java create mode 100644 app/src/main/java/de/blinkt/openvpn/fragments/Settings_Basic.java create mode 100644 app/src/main/java/de/blinkt/openvpn/fragments/Settings_IP.java create mode 100644 app/src/main/java/de/blinkt/openvpn/fragments/Settings_Obscure.java create mode 100644 app/src/main/java/de/blinkt/openvpn/fragments/Settings_Routing.java create mode 100644 app/src/main/java/de/blinkt/openvpn/fragments/ShowConfigFragment.java create mode 100644 app/src/main/java/de/blinkt/openvpn/fragments/Utils.java create mode 100644 app/src/main/java/de/blinkt/openvpn/fragments/VPNProfileList.java create mode 100644 app/src/main/java/de/blinkt/openvpn/views/FileSelectLayout.java create mode 100644 app/src/main/java/de/blinkt/openvpn/views/RemoteCNPreference.java create mode 100644 app/src/main/java/de/blinkt/openvpn/views/SeekBarTicks.java (limited to 'app/src/main/java/de/blinkt/openvpn') diff --git a/app/src/main/java/de/blinkt/openvpn/FileProvider.java b/app/src/main/java/de/blinkt/openvpn/FileProvider.java new file mode 100644 index 00000000..9ffd6545 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/FileProvider.java @@ -0,0 +1,140 @@ +package de.blinkt.openvpn; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import android.content.ContentProvider; +import android.content.ContentProvider.PipeDataWriter; +import android.content.ContentValues; +import android.content.res.AssetFileDescriptor; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.ParcelFileDescriptor; +import android.provider.OpenableColumns; +import android.util.Log; +import de.blinkt.openvpn.core.VpnStatus; + +/** + * A very simple content provider that can serve arbitrary asset files from + * our .apk. + */ +public class FileProvider extends ContentProvider +implements PipeDataWriter { + @Override + public boolean onCreate() { + return true; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + try { + File dumpfile = getFileFromURI(uri); + + + MatrixCursor c = new MatrixCursor(projection); + + Object[] row = new Object[projection.length]; + int i=0; + for (String r:projection) { + if(r.equals(OpenableColumns.SIZE)) + row[i] = dumpfile.length(); + if(r.equals(OpenableColumns.DISPLAY_NAME)) + row[i] = dumpfile.getName(); + i++; + } + c.addRow(row); + return c; + } catch (FileNotFoundException e) { + VpnStatus.logException(e); + return null; + } + + + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + // Don't support inserts. + return null; + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + // Don't support deletes. + return 0; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + // Don't support updates. + return 0; + } + + @Override + public String getType(Uri uri) { + // For this sample, assume all files are .apks. + return "application/octet-stream"; + } + + @Override + public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException { + File dumpfile = getFileFromURI(uri); + + try { + + InputStream is = new FileInputStream(dumpfile); + // Start a new thread that pipes the stream data back to the caller. + return new AssetFileDescriptor( + openPipeHelper(uri, null, null, is, this), 0, + dumpfile.length()); + } catch (IOException e) { + throw new FileNotFoundException("Unable to open minidump " + uri); + } + } + + private File getFileFromURI(Uri uri) throws FileNotFoundException { + // Try to open an asset with the given name. + String path = uri.getPath(); + if(path.startsWith("/")) + path = path.replaceFirst("/", ""); + + // I think this already random enough, no need for magic secure cookies + // 1f9563a4-a1f5-2165-255f2219-111823ef.dmp + if (!path.matches("^[0-9a-z-.]*(dmp|dmp.log)$")) + throw new FileNotFoundException("url not in expect format " + uri); + File cachedir = getContext().getCacheDir(); + return new File(cachedir,path); + } + + @Override + public void writeDataToPipe(ParcelFileDescriptor output, Uri uri, String mimeType, + Bundle opts, InputStream args) { + // Transfer data from the asset to the pipe the client is reading. + byte[] buffer = new byte[8192]; + int n; + FileOutputStream fout = new FileOutputStream(output.getFileDescriptor()); + try { + while ((n=args.read(buffer)) >= 0) { + fout.write(buffer, 0, n); + } + } catch (IOException e) { + Log.i("OpenVPNFileProvider", "Failed transferring", e); + } finally { + try { + args.close(); + } catch (IOException e) { + } + try { + fout.close(); + } catch (IOException e) { + } + } + } +} diff --git a/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java b/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java new file mode 100644 index 00000000..7aed4e4a --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java @@ -0,0 +1,295 @@ +package de.blinkt.openvpn; + +import java.io.IOException; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.ActivityNotFoundException; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.VpnService; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.text.InputType; +import android.text.TextUtils; +import android.text.method.PasswordTransformationMethod; +import android.view.View; +import android.widget.*; + +import de.blinkt.openvpn.activities.LogWindow; +import de.blinkt.openvpn.core.VpnStatus; +import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus; +import de.blinkt.openvpn.core.ProfileManager; +import de.blinkt.openvpn.core.VPNLaunchHelper; + +/** + * This Activity actually handles two stages of a launcher shortcut's life cycle. + * + * 1. Your application offers to provide shortcuts to the launcher. When + * the user installs a shortcut, an activity within your application + * generates the actual shortcut and returns it to the launcher, where it + * is shown to the user as an icon. + * + * 2. Any time the user clicks on an installed shortcut, an intent is sent. + * Typically this would then be handled as necessary by an activity within + * your application. + * + * We handle stage 1 (creating a shortcut) by simply sending back the information (in the form + * of an {@link android.content.Intent} that the launcher will use to create the shortcut. + * + * You can also implement this in an interactive way, by having your activity actually present + * UI for the user to select the specific nature of the shortcut, such as a contact, picture, URL, + * media item, or action. + * + * We handle stage 2 (responding to a shortcut) in this sample by simply displaying the contents + * of the incoming {@link android.content.Intent}. + * + * In a real application, you would probably use the shortcut intent to display specific content + * or start a particular operation. + */ +public class LaunchVPN extends Activity { + + public static final String EXTRA_KEY = "de.blinkt.openvpn.shortcutProfileUUID"; + public static final String EXTRA_NAME = "de.blinkt.openvpn.shortcutProfileName"; + public static final String EXTRA_HIDELOG = "de.blinkt.openvpn.showNoLogWindow"; + + private static final int START_VPN_PROFILE= 70; + + + private ProfileManager mPM; + private VpnProfile mSelectedProfile; + private boolean mhideLog=false; + + private boolean mCmfixed=false; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + mPM =ProfileManager.getInstance(this); + + } + + @Override + protected void onStart() { + super.onStart(); + // Resolve the intent + + final Intent intent = getIntent(); + final String action = intent.getAction(); + + // If the intent is a request to create a shortcut, we'll do that and exit + + + if(Intent.ACTION_MAIN.equals(action)) { + // we got called to be the starting point, most likely a shortcut + String shortcutUUID = intent.getStringExtra( EXTRA_KEY); + String shortcutName = intent.getStringExtra( EXTRA_NAME); + mhideLog = intent.getBooleanExtra(EXTRA_HIDELOG, false); + + VpnProfile profileToConnect = ProfileManager.get(this,shortcutUUID); + if(shortcutName != null && profileToConnect ==null) + profileToConnect = ProfileManager.getInstance(this).getProfileByName(shortcutName); + + if(profileToConnect ==null) { + VpnStatus.logError(R.string.shortcut_profile_notfound); + // show Log window to display error + showLogWindow(); + finish(); + return; + } + + mSelectedProfile = profileToConnect; + launchVPN(); + + } + } + + private void askForPW(final int type) { + + final EditText entry = new EditText(this); + final View userpwlayout = getLayoutInflater().inflate(R.layout.userpass, null); + + entry.setSingleLine(); + entry.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + entry.setTransformationMethod(new PasswordTransformationMethod()); + + AlertDialog.Builder dialog = new AlertDialog.Builder(this); + dialog.setTitle("Need " + getString(type)); + dialog.setMessage("Enter the password for profile " + mSelectedProfile.mName); + + if (type == R.string.password) { + ((EditText)userpwlayout.findViewById(R.id.username)).setText(mSelectedProfile.mUsername); + ((EditText)userpwlayout.findViewById(R.id.password)).setText(mSelectedProfile.mPassword); + ((CheckBox)userpwlayout.findViewById(R.id.save_password)).setChecked(!TextUtils.isEmpty(mSelectedProfile.mPassword)); + ((CheckBox)userpwlayout.findViewById(R.id.show_password)).setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) + ((EditText)userpwlayout.findViewById(R.id.password)).setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); + else + ((EditText)userpwlayout.findViewById(R.id.password)).setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + } + }); + + dialog.setView(userpwlayout); + } else { + dialog.setView(entry); + } + + AlertDialog.Builder builder = dialog.setPositiveButton(android.R.string.ok, + new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + + if (type == R.string.password) { + mSelectedProfile.mUsername = ((EditText) userpwlayout.findViewById(R.id.username)).getText().toString(); + + String pw = ((EditText) userpwlayout.findViewById(R.id.password)).getText().toString(); + if (((CheckBox) userpwlayout.findViewById(R.id.save_password)).isChecked()) { + mSelectedProfile.mPassword=pw; + } else { + mSelectedProfile.mPassword=null; + mSelectedProfile.mTransientPW = pw; + } + } else { + mSelectedProfile.mTransientPCKS12PW = entry.getText().toString(); + } + onActivityResult(START_VPN_PROFILE, Activity.RESULT_OK, null); + + } + + }); + dialog.setNegativeButton(android.R.string.cancel, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + VpnStatus.updateStateString("USER_VPN_PASSWORD_CANCELLED", "", R.string.state_user_vpn_password_cancelled, + ConnectionStatus.LEVEL_NOTCONNECTED); + finish(); + } + }); + + dialog.create().show(); + + } + @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) { + int needpw = mSelectedProfile.needUserPWInput(); + if(needpw !=0) { + VpnStatus.updateStateString("USER_VPN_PASSWORD", "", R.string.state_user_vpn_password, + ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT); + askForPW(needpw); + } else { + 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); + startLW.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); + startActivity(startLW); + + } + + void showConfigErrorDialog(int vpnok) { + AlertDialog.Builder d = new AlertDialog.Builder(this); + d.setTitle(R.string.config_error_found); + d.setMessage(vpnok); + d.setPositiveButton(android.R.string.ok, new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + + } + }); + d.show(); + } + + void launchVPN () { + int vpnok = mSelectedProfile.checkProfile(this); + if(vpnok!= R.string.no_error_found) { + showConfigErrorDialog(vpnok); + return; + } + + Intent intent = VpnService.prepare(this); + // Check if we want to fix /dev/tun + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + boolean usecm9fix = prefs.getBoolean("useCM9Fix", false); + boolean loadTunModule = prefs.getBoolean("loadTunModule", false); + + if(loadTunModule) + execeuteSUcmd("insmod /system/lib/modules/tun.ko"); + + if(usecm9fix && !mCmfixed ) { + execeuteSUcmd("chown system /dev/tun"); + } + + + if (intent != null) { + VpnStatus.updateStateString("USER_VPN_PERMISSION", "", R.string.state_user_vpn_permission, + ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT); + // Start the query + try { + startActivityForResult(intent, START_VPN_PROFILE); + } catch (ActivityNotFoundException ane) { + // Shame on you Sony! At least one user reported that + // an official Sony Xperia Arc S image triggers this exception + VpnStatus.logError(R.string.no_vpn_support_image); + showLogWindow(); + } + } else { + onActivityResult(START_VPN_PROFILE, Activity.RESULT_OK, null); + } + + } + + private void execeuteSUcmd(String command) { + ProcessBuilder pb = new ProcessBuilder("su","-c",command); + try { + Process p = pb.start(); + int ret = p.waitFor(); + if(ret ==0) + mCmfixed=true; + } catch (InterruptedException e) { + VpnStatus.logException("SU command", e); + + } catch (IOException e) { + VpnStatus.logException("SU command", e); + } + } + + private class startOpenVpnThread extends Thread { + + @Override + public void run() { + VPNLaunchHelper.startOpenVpn(mSelectedProfile, getBaseContext()); + finish(); + + } + + } + + +} diff --git a/app/src/main/java/de/blinkt/openvpn/OnBootReceiver.java b/app/src/main/java/de/blinkt/openvpn/OnBootReceiver.java new file mode 100644 index 00000000..32d14b1b --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/OnBootReceiver.java @@ -0,0 +1,34 @@ +package de.blinkt.openvpn; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import de.blinkt.openvpn.core.ProfileManager; + + +public class OnBootReceiver extends BroadcastReceiver { + + // Debug: am broadcast -a android.intent.action.BOOT_COMPLETED + @Override + public void onReceive(Context context, Intent intent) { + + final String action = intent.getAction(); + + if(Intent.ACTION_BOOT_COMPLETED.equals(action)) { + VpnProfile bootProfile = ProfileManager.getOnBootProfile(context); + if(bootProfile != null) { + launchVPN(bootProfile, context); + } + } + } + + void launchVPN(VpnProfile profile, Context context) { + Intent startVpnIntent = new Intent(Intent.ACTION_MAIN); + startVpnIntent.setClass(context, LaunchVPN.class); + startVpnIntent.putExtra(LaunchVPN.EXTRA_KEY,profile.getUUIDString()); + startVpnIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startVpnIntent.putExtra(LaunchVPN.EXTRA_HIDELOG, true); + + context.startActivity(startVpnIntent); + } +} diff --git a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java new file mode 100644 index 00000000..601fb2df --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java @@ -0,0 +1,942 @@ +package de.blinkt.openvpn; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Build; +import android.preference.PreferenceManager; +import android.security.KeyChain; +import android.security.KeyChainException; +import android.util.Base64; + +import de.blinkt.openvpn.core.NativeUtils; +import de.blinkt.openvpn.core.VpnStatus; +import de.blinkt.openvpn.core.OpenVpnService; +import de.blinkt.openvpn.core.X509Utils; +import org.spongycastle.util.io.pem.PemObject; +import org.spongycastle.util.io.pem.PemWriter; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import java.io.*; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Collection; +import java.util.Locale; +import java.util.UUID; +import java.util.Vector; + +public class VpnProfile implements Serializable { + // Note that this class cannot be moved to core where it belongs since + // the profile loading depends on it being here + // The Serializable documentation mentions that class name change are possible + // but the how is unclear + // + transient public static final long MAX_EMBED_FILE_SIZE = 2048*1024; // 2048kB + // Don't change this, not all parts of the program use this constant + public static final String EXTRA_PROFILEUUID = "de.blinkt.openvpn.profileUUID"; + public static final String INLINE_TAG = "[[INLINE]]"; + public static final String DISPLAYNAME_TAG = "[[NAME]]"; + public static final String MINIVPN = "miniopenvpn"; + private static final long serialVersionUID = 7085688938959334563L; + private static final String OVPNCONFIGFILE = "android.conf"; + public static final int MAXLOGLEVEL = 4; + public static final int CURRENT_PROFILE_VERSION = 2; + public static String DEFAULT_DNS1 = "8.8.8.8"; + public static String DEFAULT_DNS2 = "8.8.4.4"; + + public transient String mTransientPW = null; + public transient String mTransientPCKS12PW = null; + + + public static final int TYPE_CERTIFICATES = 0; + public static final int TYPE_PKCS12 = 1; + public static final int TYPE_KEYSTORE = 2; + public static final int TYPE_USERPASS = 3; + public static final int TYPE_STATICKEYS = 4; + public static final int TYPE_USERPASS_CERTIFICATES = 5; + public static final int TYPE_USERPASS_PKCS12 = 6; + public static final int TYPE_USERPASS_KEYSTORE = 7; + public static final int X509_VERIFY_TLSREMOTE = 0; + public static final int X509_VERIFY_TLSREMOTE_COMPAT_NOREMAPPING = 1; + public static final int X509_VERIFY_TLSREMOTE_DN = 2; + public static final int X509_VERIFY_TLSREMOTE_RDN = 3; + public static final int X509_VERIFY_TLSREMOTE_RDN_PREFIX = 4; + // variable named wrong and should haven beeen transient + // but needs to keep wrong name to guarante loading of old + // profiles + public transient boolean profileDleted = false; + public int mAuthenticationType = TYPE_KEYSTORE; + public String mName; + public String mAlias; + public String mClientCertFilename; + public String mTLSAuthDirection = ""; + public String mTLSAuthFilename; + public String mClientKeyFilename; + public String mCaFilename; + public boolean mUseLzo = true; + public String mServerPort = "1194"; + public boolean mUseUdp = true; + public String mPKCS12Filename; + public String mPKCS12Password; + public boolean mUseTLSAuth = false; + public String mServerName = "openvpn.blinkt.de"; + public String mDNS1 = DEFAULT_DNS1; + public String mDNS2 = DEFAULT_DNS2; + public String mIPv4Address; + public String mIPv6Address; + public boolean mOverrideDNS = false; + public String mSearchDomain = "blinkt.de"; + public boolean mUseDefaultRoute = true; + public boolean mUsePull = true; + public String mCustomRoutes; + public boolean mCheckRemoteCN = false; + public boolean mExpectTLSCert = true; + public String mRemoteCN = ""; + public String mPassword = ""; + public String mUsername = ""; + public boolean mRoutenopull = false; + public boolean mUseRandomHostname = false; + public boolean mUseFloat = false; + public boolean mUseCustomConfig = false; + public String mCustomConfigOptions = ""; + public String mVerb = "1"; //ignored + public String mCipher = ""; + public boolean mNobind = false; + public boolean mUseDefaultRoutev6 = true; + public String mCustomRoutesv6 = ""; + public String mKeyPassword = ""; + public boolean mPersistTun = false; + public String mConnectRetryMax = "5"; + public String mConnectRetry = "5"; + public boolean mUserEditable = true; + public String mAuth = ""; + public int mX509AuthType = X509_VERIFY_TLSREMOTE_RDN; + private transient PrivateKey mPrivateKey; + // Public attributes, since I got mad with getter/setter + // set members to default values + private UUID mUuid; + public boolean mAllowLocalLAN; + private int mProfileVersion; + public String mExcludedRoutes; + public String mExcludedRoutesv6; + + public VpnProfile(String name) { + mUuid = UUID.randomUUID(); + mName = name; + mProfileVersion = CURRENT_PROFILE_VERSION; + } + + public static String openVpnEscape(String unescaped) { + if (unescaped == null) + return null; + String escapedString = unescaped.replace("\\", "\\\\"); + escapedString = escapedString.replace("\"", "\\\""); + escapedString = escapedString.replace("\n", "\\n"); + + if (escapedString.equals(unescaped) && !escapedString.contains(" ") && + !escapedString.contains("#") && !escapedString.contains(";")) + return unescaped; + else + return '"' + escapedString + '"'; + } + + public void clearDefaults() { + mServerName = "unknown"; + mUsePull = false; + mUseLzo = false; + mUseDefaultRoute = false; + mUseDefaultRoutev6 = false; + mExpectTLSCert = false; + mPersistTun = false; + mAllowLocalLAN = true; + } + + public UUID getUUID() { + return mUuid; + + } + + public String getName() { + if (mName==null) + return "No profile name"; + return mName; + } + + public void upgradeProfile(){ + if(mProfileVersion< 2) { + /* default to the behaviour the OS used */ + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) + mAllowLocalLAN = true; + else + mAllowLocalLAN = false; + } + + mProfileVersion= CURRENT_PROFILE_VERSION; + } + + public String getConfigFile(Context context, boolean configForOvpn3) { + + File cacheDir = context.getCacheDir(); + String cfg = ""; + + // Enable managment interface + cfg += "# Enables connection to GUI\n"; + cfg += "management "; + + cfg += cacheDir.getAbsolutePath() + "/" + "mgmtsocket"; + cfg += " unix\n"; + cfg += "management-client\n"; + // Not needed, see updated man page in 2.3 + //cfg += "management-signal\n"; + cfg += "management-query-passwords\n"; + cfg += "management-hold\n\n"; + + if (!configForOvpn3) + cfg += String.format("setenv IV_GUI_VER %s \n", openVpnEscape(getVersionEnvString(context))); + + cfg += "machine-readable-output\n"; + + + boolean useTLSClient = (mAuthenticationType != TYPE_STATICKEYS); + + if (useTLSClient && mUsePull) + cfg += "client\n"; + else if (mUsePull) + cfg += "pull\n"; + else if (useTLSClient) + cfg += "tls-client\n"; + + + //cfg += "verb " + mVerb + "\n"; + cfg += "verb " + MAXLOGLEVEL + "\n"; + + if (mConnectRetryMax == null) { + mConnectRetryMax = "5"; + } + + if (!mConnectRetryMax.equals("-1")) + cfg += "connect-retry-max " + mConnectRetryMax + "\n"; + + if (mConnectRetry == null) + mConnectRetry = "5"; + + + cfg += "connect-retry " + mConnectRetry + "\n"; + + cfg += "resolv-retry 60\n"; + + + // We cannot use anything else than tun + cfg += "dev tun\n"; + + // Server Address + cfg += "remote "; + cfg += mServerName; + cfg += " "; + cfg += mServerPort; + if (mUseUdp) + cfg += " udp\n"; + else + cfg += " tcp-client\n"; + + + switch (mAuthenticationType) { + case VpnProfile.TYPE_USERPASS_CERTIFICATES: + cfg += "auth-user-pass\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"; + case VpnProfile.TYPE_PKCS12: + cfg += insertFileData("pkcs12", mPKCS12Filename); + break; + + case VpnProfile.TYPE_USERPASS_KEYSTORE: + cfg += "auth-user-pass\n"; + case VpnProfile.TYPE_KEYSTORE: + if (!configForOvpn3) { + String[] ks = getKeyStoreCertificates(context); + cfg += "### From Keystore ####\n"; + if (ks != null) { + cfg += "\n" + ks[0] + "\n\n"; + if (ks[1] != null) + cfg += "\n" + ks[1] + "\n\n"; + cfg += "\n" + ks[2] + "\n\n"; + cfg += "management-external-key\n"; + } else { + cfg += context.getString(R.string.keychain_access) + "\n"; + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) + if (!mAlias.matches("^[a-zA-Z0-9]$")) + cfg += context.getString(R.string.jelly_keystore_alphanumeric_bug) + "\n"; + } + } + break; + case VpnProfile.TYPE_USERPASS: + cfg += "auth-user-pass\n"; + cfg += insertFileData("ca", mCaFilename); + } + + if (mUseLzo) { + cfg += "comp-lzo\n"; + } + + if (mUseTLSAuth) { + if (mAuthenticationType == TYPE_STATICKEYS) + cfg += insertFileData("secret", mTLSAuthFilename); + else + cfg += insertFileData("tls-auth", mTLSAuthFilename); + + if (nonNull(mTLSAuthDirection)) { + cfg += "key-direction "; + cfg += mTLSAuthDirection; + cfg += "\n"; + } + + } + + if (!mUsePull) { + if (nonNull(mIPv4Address)) + cfg += "ifconfig " + cidrToIPAndNetmask(mIPv4Address) + "\n"; + + if (nonNull(mIPv6Address)) + cfg += "ifconfig-ipv6 " + mIPv6Address + "\n"; + } + + if (mUsePull && mRoutenopull) + cfg += "route-nopull\n"; + + String routes = ""; + + if (mUseDefaultRoute) + routes += "route 0.0.0.0 0.0.0.0 vpn_gateway\n"; + else + { + for (String route : getCustomRoutes(mCustomRoutes)) { + routes += "route " + route + " vpn_gateway\n"; + } + + for (String route: getCustomRoutes(mExcludedRoutes)) { + routes += "route " + route + " net_gateway\n"; + } + } + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT && !mAllowLocalLAN) + cfg+="redirect-private block-local\n"; + else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && mAllowLocalLAN) + cfg+="redirect-private unblock-local\n"; + + + if (mUseDefaultRoutev6) + cfg += "route-ipv6 ::/0\n"; + else + for (String route : getCustomRoutesv6(mCustomRoutesv6)) { + routes += "route-ipv6 " + route + "\n"; + } + + cfg += routes; + + if (mOverrideDNS || !mUsePull) { + if (nonNull(mDNS1)) + cfg += "dhcp-option DNS " + mDNS1 + "\n"; + if (nonNull(mDNS2)) + cfg += "dhcp-option DNS " + mDNS2 + "\n"; + if (nonNull(mSearchDomain)) + cfg += "dhcp-option DOMAIN " + mSearchDomain + "\n"; + + } + + if (mNobind) + cfg += "nobind\n"; + + + // Authentication + if (mAuthenticationType != TYPE_STATICKEYS) { + if (mCheckRemoteCN) { + if (mRemoteCN == null || mRemoteCN.equals("")) + cfg += "verify-x509-name " + mServerName + " name\n"; + else + switch (mX509AuthType) { + + // 2.2 style x509 checks + case X509_VERIFY_TLSREMOTE_COMPAT_NOREMAPPING: + cfg += "compat-names no-remapping\n"; + case X509_VERIFY_TLSREMOTE: + cfg += "tls-remote " + openVpnEscape(mRemoteCN) + "\n"; + break; + + case X509_VERIFY_TLSREMOTE_RDN: + cfg += "verify-x509-name " + openVpnEscape(mRemoteCN) + " name\n"; + break; + + case X509_VERIFY_TLSREMOTE_RDN_PREFIX: + cfg += "verify-x509-name " + openVpnEscape(mRemoteCN) + " name-prefix\n"; + break; + + case X509_VERIFY_TLSREMOTE_DN: + cfg += "verify-x509-name " + openVpnEscape(mRemoteCN) + "\n"; + break; + } + } + if (mExpectTLSCert) + cfg += "remote-cert-tls server\n"; + } + + if (nonNull(mCipher)) { + cfg += "cipher " + mCipher + "\n"; + } + + if (nonNull(mAuth)) { + cfg += "auth " + mAuth + "\n"; + } + + // Obscure Settings dialog + if (mUseRandomHostname) + cfg += "#my favorite options :)\nremote-random-hostname\n"; + + if (mUseFloat) + cfg += "float\n"; + + if (mPersistTun) { + cfg += "persist-tun\n"; + cfg += "# persist-tun also enables pre resolving to avoid DNS resolve problem\n"; + cfg += "preresolve\n"; + } + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + boolean usesystemproxy = prefs.getBoolean("usesystemproxy", true); + if (usesystemproxy) { + cfg += "# Use system proxy setting\n"; + cfg += "management-query-proxy\n"; + } + + + if (mUseCustomConfig) { + cfg += "# Custom configuration options\n"; + cfg += "# You are on your on own here :)\n"; + cfg += mCustomConfigOptions; + cfg += "\n"; + + } + + + return cfg; + } + + public String getVersionEnvString(Context c) { + String version = "unknown"; + try { + PackageInfo packageinfo = c.getPackageManager().getPackageInfo(c.getPackageName(), 0); + version = packageinfo.versionName; + } catch (PackageManager.NameNotFoundException e) { + VpnStatus.logException(e); + } + return String.format(Locale.US, "%s %s", c.getPackageName(), version); + + } + + //! Put inline data inline and other data as normal escaped filename + private String insertFileData(String cfgentry, String filedata) { + if (filedata == null) { + // TODO: generate good error + return String.format("%s %s\n", cfgentry, "missing"); + } else if (isEmbedded(filedata)) { + String dataWithOutHeader = getEmbeddedContent(filedata); + return String.format(Locale.ENGLISH, "<%s>\n%s\n\n", cfgentry, dataWithOutHeader, cfgentry); + } else { + return String.format(Locale.ENGLISH, "%s %s\n", cfgentry, openVpnEscape(filedata)); + } + } + + private boolean nonNull(String val) { + if (val == null || val.equals("")) + return false; + else + return true; + } + + private Collection getCustomRoutes(String routes) { + Vector cidrRoutes = new Vector(); + if (routes == null) { + // No routes set, return empty vector + return cidrRoutes; + } + for (String route : routes.split("[\n \t]")) { + if (!route.equals("")) { + String cidrroute = cidrToIPAndNetmask(route); + if (cidrroute == null) + return null; + + cidrRoutes.add(cidrroute); + } + } + + return cidrRoutes; + } + + private Collection getCustomRoutesv6(String routes) { + Vector cidrRoutes = new Vector(); + if (routes == null) { + // No routes set, return empty vector + return cidrRoutes; + } + for (String route : routes.split("[\n \t]")) { + if (!route.equals("")) { + cidrRoutes.add(route); + } + } + + return cidrRoutes; + } + + private String cidrToIPAndNetmask(String route) { + String[] parts = route.split("/"); + + // No /xx, assume /32 as netmask + if (parts.length == 1) + parts = (route + "/32").split("/"); + + if (parts.length != 2) + return null; + int len; + try { + len = Integer.parseInt(parts[1]); + } catch (NumberFormatException ne) { + return null; + } + if (len < 0 || len > 32) + return null; + + + long nm = 0xffffffffl; + nm = (nm << (32 - len)) & 0xffffffffl; + + String netmask = String.format(Locale.ENGLISH, "%d.%d.%d.%d", (nm & 0xff000000) >> 24, (nm & 0xff0000) >> 16, (nm & 0xff00) >> 8, nm & 0xff); + return parts[0] + " " + netmask; + } + + private String[] buildOpenvpnArgv(File cacheDir) { + Vector args = new Vector(); + + // Add fixed paramenters + //args.add("/data/data/de.blinkt.openvpn/lib/openvpn"); + args.add(cacheDir.getAbsolutePath() + "/" + VpnProfile.MINIVPN); + + args.add("--config"); + args.add(cacheDir.getAbsolutePath() + "/" + OVPNCONFIGFILE); + + + return args.toArray(new String[args.size()]); + } + + public Intent prepareIntent(Context context) { + String prefix = context.getPackageName(); + + Intent intent = new Intent(context, OpenVpnService.class); + + if (mAuthenticationType == VpnProfile.TYPE_KEYSTORE || mAuthenticationType == VpnProfile.TYPE_USERPASS_KEYSTORE) { + if (getKeyStoreCertificates(context) == null) + return null; + } + + intent.putExtra(prefix + ".ARGV", buildOpenvpnArgv(context.getCacheDir())); + intent.putExtra(prefix + ".profileUUID", mUuid.toString()); + + ApplicationInfo info = context.getApplicationInfo(); + intent.putExtra(prefix + ".nativelib", info.nativeLibraryDir); + + try { + FileWriter cfg = new FileWriter(context.getCacheDir().getAbsolutePath() + "/" + OVPNCONFIGFILE); + cfg.write(getConfigFile(context, false)); + cfg.flush(); + cfg.close(); + } catch (IOException e) { + VpnStatus.logException(e); + } + + return intent; + } + + public String[] getKeyStoreCertificates(Context context) { + return getKeyStoreCertificates(context, 5); + } + + public static String getDisplayName(String embeddedFile) { + int start = DISPLAYNAME_TAG.length(); + int end = embeddedFile.indexOf(INLINE_TAG); + return embeddedFile.substring(start,end); + } + + public static String getEmbeddedContent(String data) + { + if (!data.contains(INLINE_TAG)) + return data; + + int start = data.indexOf(INLINE_TAG) + INLINE_TAG.length(); + return data.substring(start); + } + + public static boolean isEmbedded(String data) { + if (data==null) + return false; + if(data.startsWith(INLINE_TAG) || data.startsWith(DISPLAYNAME_TAG)) + return true; + else + return false; + } + + + class NoCertReturnedException extends Exception { + public NoCertReturnedException (String msg) { + super(msg); + } + } + + synchronized String[] getKeyStoreCertificates(Context context,int tries) { + PrivateKey privateKey = null; + X509Certificate[] cachain; + Exception exp=null; + try { + privateKey = KeyChain.getPrivateKey(context, mAlias); + mPrivateKey = privateKey; + + String keystoreChain = null; + + + cachain = KeyChain.getCertificateChain(context, mAlias); + if(cachain == null) + throw new NoCertReturnedException("No certificate returned from Keystore"); + + if (cachain.length <= 1 && !nonNull(mCaFilename)) { + VpnStatus.logMessage(VpnStatus.LogLevel.ERROR, "", context.getString(R.string.keychain_nocacert)); + } else { + StringWriter ksStringWriter = new StringWriter(); + + PemWriter pw = new PemWriter(ksStringWriter); + for (int i = 1; i < cachain.length; i++) { + X509Certificate cert = cachain[i]; + pw.writeObject(new PemObject("CERTIFICATE", cert.getEncoded())); + } + pw.close(); + keystoreChain = ksStringWriter.toString(); + } + + + String caout = null; + if (nonNull(mCaFilename)) { + try { + Certificate cacert = X509Utils.getCertificateFromFile(mCaFilename); + StringWriter caoutWriter = new StringWriter(); + PemWriter pw = new PemWriter(caoutWriter); + + pw.writeObject(new PemObject("CERTIFICATE", cacert.getEncoded())); + pw.close(); + caout= caoutWriter.toString(); + + } catch (Exception e) { + VpnStatus.logError("Could not read CA certificate" + e.getLocalizedMessage()); + } + } + + + StringWriter certout = new StringWriter(); + + + if (cachain.length >= 1) { + X509Certificate usercert = cachain[0]; + + PemWriter upw = new PemWriter(certout); + upw.writeObject(new PemObject("CERTIFICATE", usercert.getEncoded())); + upw.close(); + + } + String user = certout.toString(); + + + String ca, extra; + if(caout==null) { + ca =keystoreChain; + extra=null; + } else { + ca = caout; + extra=keystoreChain; + } + + return new String[]{ca, extra, user}; + } catch (InterruptedException e) { + exp=e; + } catch (FileNotFoundException e) { + exp=e; + } catch (CertificateException e) { + exp=e; + } catch (IOException e) { + exp=e; + } catch (KeyChainException e) { + exp=e; + } catch (NoCertReturnedException e) { + exp =e; + } catch (IllegalArgumentException e) { + exp =e; + } catch (AssertionError e) { + if (tries ==0) + return null; + VpnStatus.logError(String.format("Failure getting Keystore Keys (%s), retrying",e.getLocalizedMessage())); + try { + Thread.sleep(3000); + } catch (InterruptedException e1) { + VpnStatus.logException(e1); + } + return getKeyStoreCertificates(context, tries-1); + } + if (exp != null) { + exp.printStackTrace(); + VpnStatus.logError(R.string.keyChainAccessError, exp.getLocalizedMessage()); + + VpnStatus.logError(R.string.keychain_access); + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) { + if (!mAlias.matches("^[a-zA-Z0-9]$")) { + VpnStatus.logError(R.string.jelly_keystore_alphanumeric_bug); + } + } + } + return null; + } + + //! Return an error if somethign is wrong + public int checkProfile(Context context) { + if (mAuthenticationType == TYPE_KEYSTORE || mAuthenticationType == TYPE_USERPASS_KEYSTORE) { + if (mAlias == null) + return R.string.no_keystore_cert_selected; + } + + if (!mUsePull || mAuthenticationType == TYPE_STATICKEYS) { + if (mIPv4Address == null || cidrToIPAndNetmask(mIPv4Address) == null) + return R.string.ipv4_format_error; + } + if (!mUseDefaultRoute && (getCustomRoutes(mCustomRoutes) == null || getCustomRoutes(mExcludedRoutes) ==null)) + return R.string.custom_route_format_error; + + // Everything okay + return R.string.no_error_found; + + } + + //! Openvpn asks for a "Private Key", this should be pkcs12 key + // + public String getPasswordPrivateKey() { + if (mTransientPCKS12PW != null) { + String pwcopy = mTransientPCKS12PW; + mTransientPCKS12PW = null; + return pwcopy; + } + switch (mAuthenticationType) { + case TYPE_PKCS12: + case TYPE_USERPASS_PKCS12: + return mPKCS12Password; + + case TYPE_CERTIFICATES: + case TYPE_USERPASS_CERTIFICATES: + return mKeyPassword; + + case TYPE_USERPASS: + case TYPE_STATICKEYS: + default: + return null; + } + } + + public boolean isUserPWAuth() { + switch (mAuthenticationType) { + case TYPE_USERPASS: + case TYPE_USERPASS_CERTIFICATES: + case TYPE_USERPASS_KEYSTORE: + case TYPE_USERPASS_PKCS12: + return true; + default: + return false; + + } + } + + public boolean requireTLSKeyPassword() { + if (!nonNull(mClientKeyFilename)) + return false; + + String data = ""; + if (isEmbedded(mClientKeyFilename)) + data = mClientKeyFilename; + else { + char[] buf = new char[2048]; + FileReader fr; + try { + fr = new FileReader(mClientKeyFilename); + int len = fr.read(buf); + while (len > 0) { + data += new String(buf, 0, len); + len = fr.read(buf); + } + fr.close(); + } catch (FileNotFoundException e) { + return false; + } catch (IOException e) { + return false; + } + + } + + if (data.contains("Proc-Type: 4,ENCRYPTED")) + return true; + else if (data.contains("-----BEGIN ENCRYPTED PRIVATE KEY-----")) + return true; + else + return false; + } + + public int needUserPWInput() { + if ((mAuthenticationType == TYPE_PKCS12 || mAuthenticationType == TYPE_USERPASS_PKCS12) && + (mPKCS12Password == null || mPKCS12Password.equals(""))) { + if (mTransientPCKS12PW == null) + return R.string.pkcs12_file_encryption_key; + } + + if (mAuthenticationType == TYPE_CERTIFICATES || mAuthenticationType == TYPE_USERPASS_CERTIFICATES) { + if (requireTLSKeyPassword() && !nonNull(mKeyPassword)) + if (mTransientPCKS12PW == null) { + return R.string.private_key_password; + } + } + + if (isUserPWAuth() && !(nonNull(mUsername) && (nonNull(mPassword) || mTransientPW != null))) { + return R.string.password; + } + return 0; + } + + public String getPasswordAuth() { + if (mTransientPW != null) { + String pwcopy = mTransientPW; + mTransientPW = null; + return pwcopy; + } else { + return mPassword; + } + } + + // Used by the Array Adapter + @Override + public String toString() { + return mName; + } + + public String getUUIDString() { + return mUuid.toString(); + } + + public PrivateKey getKeystoreKey() { + return mPrivateKey; + } + + public String getSignedData(String b64data) { + PrivateKey privkey = getKeystoreKey(); + Exception err; + + byte[] data = Base64.decode(b64data, Base64.DEFAULT); + + // The Jelly Bean *evil* Hack + // 4.2 implements the RSA/ECB/PKCS1PADDING in the OpenSSLprovider + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) { + return processSignJellyBeans(privkey, data); + } + + + try { + + + Cipher rsasinger = Cipher.getInstance("RSA/ECB/PKCS1PADDING"); + + rsasinger.init(Cipher.ENCRYPT_MODE, privkey); + + byte[] signed_bytes = rsasinger.doFinal(data); + return Base64.encodeToString(signed_bytes, Base64.NO_WRAP); + + } catch (NoSuchAlgorithmException e) { + err = e; + } catch (InvalidKeyException e) { + err = e; + } catch (NoSuchPaddingException e) { + err = e; + } catch (IllegalBlockSizeException e) { + err = e; + } catch (BadPaddingException e) { + err = e; + } + + VpnStatus.logError(R.string.error_rsa_sign, err.getClass().toString(), err.getLocalizedMessage()); + + return null; + + } + + private String processSignJellyBeans(PrivateKey privkey, byte[] data) { + Exception err; + try { + Method getKey = privkey.getClass().getSuperclass().getDeclaredMethod("getOpenSSLKey"); + getKey.setAccessible(true); + + // Real object type is OpenSSLKey + Object opensslkey = getKey.invoke(privkey); + + getKey.setAccessible(false); + + Method getPkeyContext = opensslkey.getClass().getDeclaredMethod("getPkeyContext"); + + // integer pointer to EVP_pkey + getPkeyContext.setAccessible(true); + int pkey = (Integer) getPkeyContext.invoke(opensslkey); + getPkeyContext.setAccessible(false); + + // 112 with TLS 1.2 (172 back with 4.3), 36 with TLS 1.0 + byte[] signed_bytes = NativeUtils.rsasign(data, pkey); + return Base64.encodeToString(signed_bytes, Base64.NO_WRAP); + + } catch (NoSuchMethodException e) { + err = e; + } catch (IllegalArgumentException e) { + err = e; + } catch (IllegalAccessException e) { + err = e; + } catch (InvocationTargetException e) { + err = e; + } catch (InvalidKeyException e) { + err = e; + } + VpnStatus.logError(R.string.error_rsa_sign, err.getClass().toString(), err.getLocalizedMessage()); + + return null; + + } + + +} + + + + diff --git a/app/src/main/java/de/blinkt/openvpn/activities/ConfigConverter.java b/app/src/main/java/de/blinkt/openvpn/activities/ConfigConverter.java new file mode 100644 index 00000000..f870e8a9 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/activities/ConfigConverter.java @@ -0,0 +1,639 @@ + +package de.blinkt.openvpn.activities; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.AlertDialog.Builder; +import android.content.ActivityNotFoundException; +import android.content.Intent; +import android.database.Cursor; +import android.os.Bundle; +import android.os.Environment; +import android.provider.OpenableColumns; +import android.security.KeyChain; +import android.security.KeyChainAliasCallback; +import android.text.TextUtils; +import android.util.Base64; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.LinearLayout; +import android.widget.TextView; + +import junit.framework.Assert; + +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Vector; + +import de.blinkt.openvpn.R; +import de.blinkt.openvpn.VpnProfile; +import de.blinkt.openvpn.core.ConfigParser; +import de.blinkt.openvpn.core.ConfigParser.ConfigParseError; +import de.blinkt.openvpn.core.ProfileManager; +import de.blinkt.openvpn.fragments.Utils; +import de.blinkt.openvpn.views.FileSelectLayout; + +import static de.blinkt.openvpn.views.FileSelectLayout.FileSelectCallback; + +public class ConfigConverter extends Activity implements FileSelectCallback { + + public static final String IMPORT_PROFILE = "de.blinkt.openvpn.IMPORT_PROFILE"; + private static final int RESULT_INSTALLPKCS12 = 7; + private static final int CHOOSE_FILE_OFFSET = 1000; + public static final String VPNPROFILE = "vpnProfile"; + + private VpnProfile mResult; + + private transient List mPathsegments; + + private String mAliasName = null; + + + private Map fileSelectMap = new HashMap(); + private String mEmbeddedPwFile; + private Vector mLogEntries = new Vector(); + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.cancel) { + setResult(Activity.RESULT_CANCELED); + finish(); + } else if (item.getItemId() == R.id.ok) { + if (mResult == null) { + log("Importing the config had error, cannot save it"); + return true; + } + + Intent in = installPKCS12(); + + if (in != null) + startActivityForResult(in, RESULT_INSTALLPKCS12); + else + saveProfile(); + + return true; + } + + return super.onOptionsItemSelected(item); + + } + + @Override + protected void onSaveInstanceState(@NotNull Bundle outState) { + super.onSaveInstanceState(outState); + if (mResult != null) + outState.putSerializable(VPNPROFILE, mResult); + outState.putString("mAliasName", mAliasName); + + + + String[] logentries = mLogEntries.toArray(new String[mLogEntries.size()]); + + outState.putStringArray("logentries", logentries); + + int[] fileselects = new int[fileSelectMap.size()]; + int k = 0; + for (Utils.FileType key : fileSelectMap.keySet()) { + fileselects[k] = key.getValue(); + k++; + } + outState.putIntArray("fileselects", fileselects); + outState.putString("pwfile",mEmbeddedPwFile); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent result) { + if (requestCode == RESULT_INSTALLPKCS12 && resultCode == Activity.RESULT_OK) { + showCertDialog(); + } + + if (resultCode == Activity.RESULT_OK && requestCode >= CHOOSE_FILE_OFFSET) { + Utils.FileType type = Utils.FileType.getFileTypeByValue(requestCode - CHOOSE_FILE_OFFSET); + + + FileSelectLayout fs = fileSelectMap.get(type); + fs.parseResponse(result, this); + + String data = fs.getData(); + + switch (type) { + case USERPW_FILE: + mEmbeddedPwFile = data; + break; + case PKCS12: + mResult.mPKCS12Filename = data; + break; + case TLS_AUTH_FILE: + mResult.mTLSAuthFilename = data; + break; + case CA_CERTIFICATE: + mResult.mCaFilename = data; + break; + case CLIENT_CERTIFICATE: + mResult.mClientCertFilename = data; + break; + case KEYFILE: + mResult.mClientKeyFilename = data; + break; + default: + Assert.fail(); + } + } + + super.onActivityResult(requestCode, resultCode, result); + } + + private void saveProfile() { + Intent result = new Intent(); + ProfileManager vpl = ProfileManager.getInstance(this); + + if (!TextUtils.isEmpty(mEmbeddedPwFile)) + ConfigParser.useEmbbedUserAuth(mResult, mEmbeddedPwFile); + + vpl.addProfile(mResult); + vpl.saveProfile(this, mResult); + vpl.saveProfileList(this); + result.putExtra(VpnProfile.EXTRA_PROFILEUUID, mResult.getUUID().toString()); + setResult(Activity.RESULT_OK, result); + finish(); + } + + public void showCertDialog() { + try { + KeyChain.choosePrivateKeyAlias(this, + new KeyChainAliasCallback() { + + public void alias(String alias) { + // Credential alias selected. Remember the alias selection for future use. + mResult.mAlias = alias; + saveProfile(); + } + + + }, + new String[]{"RSA"}, // List of acceptable key types. null for any + null, // issuer, null for any + mResult.mServerName, // host name of server requesting the cert, null if unavailable + -1, // port of server requesting the cert, -1 if unavailable + mAliasName); // alias to preselect, null if unavailable + } catch (ActivityNotFoundException anf) { + Builder ab = new AlertDialog.Builder(this); + ab.setTitle(R.string.broken_image_cert_title); + ab.setMessage(R.string.broken_image_cert); + ab.setPositiveButton(android.R.string.ok, null); + ab.show(); + } + } + + + private Intent installPKCS12() { + + if (!((CheckBox) findViewById(R.id.importpkcs12)).isChecked()) { + setAuthTypeToEmbeddedPKCS12(); + return null; + + } + String pkcs12datastr = mResult.mPKCS12Filename; + if (VpnProfile.isEmbedded(pkcs12datastr)) { + Intent inkeyIntent = KeyChain.createInstallIntent(); + + pkcs12datastr = VpnProfile.getEmbeddedContent(pkcs12datastr); + + + byte[] pkcs12data = Base64.decode(pkcs12datastr, Base64.DEFAULT); + + + inkeyIntent.putExtra(KeyChain.EXTRA_PKCS12, pkcs12data); + + if (mAliasName.equals("")) + mAliasName = null; + + if (mAliasName != null) { + inkeyIntent.putExtra(KeyChain.EXTRA_NAME, mAliasName); + } + return inkeyIntent; + + } + return null; + } + + + private void setAuthTypeToEmbeddedPKCS12() { + if (VpnProfile.isEmbedded(mResult.mPKCS12Filename)) { + if (mResult.mAuthenticationType == VpnProfile.TYPE_USERPASS_KEYSTORE) + mResult.mAuthenticationType = VpnProfile.TYPE_USERPASS_PKCS12; + + if (mResult.mAuthenticationType == VpnProfile.TYPE_KEYSTORE) + mResult.mAuthenticationType = VpnProfile.TYPE_PKCS12; + + } + } + + + private String getUniqueProfileName(String possibleName) { + + int i = 0; + + ProfileManager vpl = ProfileManager.getInstance(this); + + String newname = possibleName; + + // Default to + if (mResult.mName != null && !ConfigParser.CONVERTED_PROFILE.equals(mResult.mName)) + newname = mResult.mName; + + while (newname == null || vpl.getProfileByName(newname) != null) { + i++; + if (i == 1) + newname = getString(R.string.converted_profile); + else + newname = getString(R.string.converted_profile_i, i); + } + + return newname; + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.import_menu, menu); + return true; + } + + private String embedFile(String filename, Utils.FileType type) { + if (filename == null) + return null; + + // Already embedded, nothing to do + if (VpnProfile.isEmbedded(filename)) + return filename; + + File possibleFile = findFile(filename, type); + if (possibleFile == null) + return filename; + else + return readFileContent(possibleFile, type == Utils.FileType.PKCS12); + + } + + private File findFile(String filename, Utils.FileType fileType) { + File foundfile = findFileRaw(filename); + + if (foundfile == null && filename != null && !filename.equals("")) { + log(R.string.import_could_not_open, filename); + addFileSelectDialog(fileType); + } + + + return foundfile; + } + + private void addFileSelectDialog(Utils.FileType type) { + int titleRes = 0; + String value = null; + switch (type) { + case KEYFILE: + titleRes = R.string.client_key_title; + if (mResult != null) + value = mResult.mClientKeyFilename; + break; + case CLIENT_CERTIFICATE: + titleRes = R.string.client_certificate_title; + if (mResult != null) + value = mResult.mClientCertFilename; + break; + case CA_CERTIFICATE: + titleRes = R.string.ca_title; + if (mResult != null) + value = mResult.mCaFilename; + break; + case TLS_AUTH_FILE: + titleRes = R.string.tls_auth_file; + if (mResult != null) + value = mResult.mTLSAuthFilename; + break; + case PKCS12: + titleRes = R.string.client_pkcs12_title; + if (mResult != null) + value = mResult.mPKCS12Filename; + break; + + case USERPW_FILE: + titleRes = R.string.userpw_file; + value = mEmbeddedPwFile; + break; + + } + + boolean isCert = type == Utils.FileType.CA_CERTIFICATE || type == Utils.FileType.CLIENT_CERTIFICATE; + FileSelectLayout fl = new FileSelectLayout(this, getString(titleRes), isCert); + fileSelectMap.put(type, fl); + fl.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + + ((LinearLayout) findViewById(R.id.config_convert_root)).addView(fl, 2); + findViewById(R.id.files_missing_hint).setVisibility(View.VISIBLE); + fl.setData(value, this); + int i = getFileLayoutOffset(type); + fl.setCaller(this, i, type); + + } + + private int getFileLayoutOffset(Utils.FileType type) { + return CHOOSE_FILE_OFFSET + type.getValue(); + } + + + private File findFileRaw(String filename) { + if (filename == null || filename.equals("")) + return null; + + // Try diffent path relative to /mnt/sdcard + File sdcard = Environment.getExternalStorageDirectory(); + File root = new File("/"); + + HashSet dirlist = new HashSet(); + + for (int i = mPathsegments.size() - 1; i >= 0; i--) { + String path = ""; + for (int j = 0; j <= i; j++) { + path += "/" + mPathsegments.get(j); + } + // Do a little hackish dance for the Android File Importer + // /document/primary:ovpn/openvpn-imt.conf + + + if (path.indexOf(':') != -1 && path.indexOf('/') > path.indexOf(':')) { + String possibleDir = path.substring(path.indexOf(':') + 1, path.length()); + possibleDir = possibleDir.substring(0, possibleDir.lastIndexOf('/')); + + + dirlist.add(new File(sdcard, possibleDir)); + + } + dirlist.add(new File(path)); + + + } + dirlist.add(sdcard); + dirlist.add(root); + + + String[] fileparts = filename.split("/"); + for (File rootdir : dirlist) { + String suffix = ""; + for (int i = fileparts.length - 1; i >= 0; i--) { + if (i == fileparts.length - 1) + suffix = fileparts[i]; + else + suffix = fileparts[i] + "/" + suffix; + + File possibleFile = new File(rootdir, suffix); + if (!possibleFile.canRead()) + continue; + + // read the file inline + return possibleFile; + + } + } + return null; + } + + String readFileContent(File possibleFile, boolean base64encode) { + byte[] filedata; + try { + filedata = readBytesFromFile(possibleFile); + } catch (IOException e) { + log(e.getLocalizedMessage()); + return null; + } + + String data; + if (base64encode) { + data = Base64.encodeToString(filedata, Base64.DEFAULT); + } else { + data = new String(filedata); + + } + + return VpnProfile.DISPLAYNAME_TAG + possibleFile.getName() + VpnProfile.INLINE_TAG + data; + + } + + + private byte[] readBytesFromFile(File file) throws IOException { + InputStream input = new FileInputStream(file); + + long len = file.length(); + if (len > VpnProfile.MAX_EMBED_FILE_SIZE) + throw new IOException("File size of file to import too large."); + + // Create the byte array to hold the data + byte[] bytes = new byte[(int) len]; + + // Read in the bytes + int offset = 0; + int bytesRead; + while (offset < bytes.length + && (bytesRead = input.read(bytes, offset, bytes.length - offset)) >= 0) { + offset += bytesRead; + } + + input.close(); + return bytes; + } + + void embedFiles() { + // This where I would like to have a c++ style + // void embedFile(std::string & option) + + if (mResult.mPKCS12Filename != null) { + File pkcs12file = findFileRaw(mResult.mPKCS12Filename); + if (pkcs12file != null) { + mAliasName = pkcs12file.getName().replace(".p12", ""); + } else { + mAliasName = "Imported PKCS12"; + } + } + + + mResult.mCaFilename = embedFile(mResult.mCaFilename, Utils.FileType.CA_CERTIFICATE); + mResult.mClientCertFilename = embedFile(mResult.mClientCertFilename, Utils.FileType.CLIENT_CERTIFICATE); + mResult.mClientKeyFilename = embedFile(mResult.mClientKeyFilename, Utils.FileType.KEYFILE); + mResult.mTLSAuthFilename = embedFile(mResult.mTLSAuthFilename, Utils.FileType.TLS_AUTH_FILE); + mResult.mPKCS12Filename = embedFile(mResult.mPKCS12Filename, Utils.FileType.PKCS12); + mEmbeddedPwFile = embedFile(mResult.mPassword, Utils.FileType.USERPW_FILE); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + + setContentView(R.layout.config_converter); + super.onCreate(savedInstanceState); + + if (savedInstanceState != null && savedInstanceState.containsKey(VPNPROFILE)) { + mResult = (VpnProfile) savedInstanceState.getSerializable(VPNPROFILE); + mAliasName = savedInstanceState.getString("mAliasName"); + mEmbeddedPwFile = savedInstanceState.getString("pwfile"); + + if (savedInstanceState.containsKey("logentries")) { + //noinspection ConstantConditions + for (String logItem : savedInstanceState.getStringArray("logentries")) + log(logItem); + } + if (savedInstanceState.containsKey("fileselects")) { + //noinspection ConstantConditions + for (int k : savedInstanceState.getIntArray("fileselects")) { + addFileSelectDialog(Utils.FileType.getFileTypeByValue(k)); + } + } + return; + } + + + final android.content.Intent intent = getIntent(); + + if (intent != null) { + final android.net.Uri data = intent.getData(); + if (data != null) { + //log(R.string.import_experimental); + log(R.string.importing_config, data.toString()); + try { + String possibleName = null; + if ((data.getScheme() != null && data.getScheme().equals("file")) || + (data.getLastPathSegment() != null && + (data.getLastPathSegment().endsWith(".ovpn") || + data.getLastPathSegment().endsWith(".conf"))) + ) { + possibleName = data.getLastPathSegment(); + if (possibleName.lastIndexOf('/') != -1) + possibleName = possibleName.substring(possibleName.lastIndexOf('/') + 1); + + } + InputStream is = getContentResolver().openInputStream(data); + mPathsegments = data.getPathSegments(); + + Cursor cursor = null; + if (data!=null) + cursor = getContentResolver().query(data, null, null, null, null); + + + try { + + + if (cursor!=null && cursor.moveToFirst()) { + int columnIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); + + if (columnIndex != -1) { + String displayName = cursor.getString(columnIndex); + if (displayName != null) + possibleName = displayName; + } + columnIndex = cursor.getColumnIndex("mime_type"); + if (columnIndex != -1) { + log("Opening Mime TYPE: " + cursor.getString(columnIndex)); + } + } + } finally { + if(cursor!=null) + cursor.close(); + } + if (possibleName != null) { + possibleName = possibleName.replace(".ovpn", ""); + possibleName = possibleName.replace(".conf", ""); + } + + doImport(is, possibleName); + + } catch (FileNotFoundException e) { + log(R.string.import_content_resolve_error); + } + } + + // We parsed the intent, relay on saved instance for restoring + setIntent(null); + } + + + } + + + @Override + protected void onStart() { + super.onStart(); + + + } + + private void log(String logmessage) { + mLogEntries.add(logmessage); + TextView tv = new TextView(this); + tv.setText(logmessage); + LinearLayout logLayout = (LinearLayout) findViewById(R.id.config_convert_root); + logLayout.addView(tv); + } + + private void doImport(InputStream is, String newName) { + ConfigParser cp = new ConfigParser(); + try { + InputStreamReader isr = new InputStreamReader(is); + + cp.parseConfig(isr); + mResult = cp.convertProfile(); + embedFiles(); + displayWarnings(); + mResult.mName = getUniqueProfileName(newName); + + log(R.string.import_done); + return; + + } catch (IOException e) { + log(R.string.error_reading_config_file); + log(e.getLocalizedMessage()); + } catch (ConfigParseError e) { + log(R.string.error_reading_config_file); + log(e.getLocalizedMessage()); + } + mResult = null; + + } + + private void displayWarnings() { + if (mResult.mUseCustomConfig) { + log(R.string.import_warning_custom_options); + String copt = mResult.mCustomConfigOptions; + if (copt.startsWith("#")) { + int until = copt.indexOf('\n'); + copt = copt.substring(until + 1); + } + + log(copt); + } + + if (mResult.mAuthenticationType == VpnProfile.TYPE_KEYSTORE || + mResult.mAuthenticationType == VpnProfile.TYPE_USERPASS_KEYSTORE) { + findViewById(R.id.importpkcs12).setVisibility(View.VISIBLE); + } + + } + + private void log(int ressourceId, Object... formatArgs) { + log(getString(ressourceId, formatArgs)); + } + + +} diff --git a/app/src/main/java/de/blinkt/openvpn/activities/CreateShortcuts.java b/app/src/main/java/de/blinkt/openvpn/activities/CreateShortcuts.java new file mode 100644 index 00000000..53a829ff --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/activities/CreateShortcuts.java @@ -0,0 +1,154 @@ +package de.blinkt.openvpn.activities; + +import android.app.ListActivity; +import android.content.Intent; +import android.os.Bundle; +import android.os.Parcelable; +import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.TextView; +import de.blinkt.openvpn.LaunchVPN; +import de.blinkt.openvpn.R; +import de.blinkt.openvpn.VpnProfile; +import de.blinkt.openvpn.core.ProfileManager; + +import java.util.Collection; +import java.util.Vector; + +/** + * This Activity actually handles two stages of a launcher shortcut's life cycle. + * + * 1. Your application offers to provide shortcuts to the launcher. When + * the user installs a shortcut, an activity within your application + * generates the actual shortcut and returns it to the launcher, where it + * is shown to the user as an icon. + * + * 2. Any time the user clicks on an installed shortcut, an intent is sent. + * Typically this would then be handled as necessary by an activity within + * your application. + * + * We handle stage 1 (creating a shortcut) by simply sending back the information (in the form + * of an {@link android.content.Intent} that the launcher will use to create the shortcut. + * + * You can also implement this in an interactive way, by having your activity actually present + * UI for the user to select the specific nature of the shortcut, such as a contact, picture, URL, + * media item, or action. + * + * We handle stage 2 (responding to a shortcut) in this sample by simply displaying the contents + * of the incoming {@link android.content.Intent}. + * + * In a real application, you would probably use the shortcut intent to display specific content + * or start a particular operation. + */ +public class CreateShortcuts extends ListActivity implements OnItemClickListener { + + + private static final int START_VPN_PROFILE= 70; + + + private ProfileManager mPM; + private VpnProfile mSelectedProfile; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + mPM =ProfileManager.getInstance(this); + + } + + @Override + protected void onStart() { + super.onStart(); + // Resolve the intent + + createListView(); + } + + private void createListView() { + ListView lv = getListView(); + //lv.setTextFilterEnabled(true); + + Collection vpnList = mPM.getProfiles(); + + Vector vpnNames=new Vector(); + for (VpnProfile vpnProfile : vpnList) { + vpnNames.add(vpnProfile.mName); + } + + + + ArrayAdapter adapter = new ArrayAdapter(this,android.R.layout.simple_list_item_1,vpnNames); + lv.setAdapter(adapter); + + lv.setOnItemClickListener(this); + } + + /** + * This function creates a shortcut and returns it to the caller. There are actually two + * intents that you will send back. + * + * The first intent serves as a container for the shortcut and is returned to the launcher by + * setResult(). This intent must contain three fields: + * + *
    + *
  • {@link android.content.Intent#EXTRA_SHORTCUT_INTENT} The shortcut intent.
  • + *
  • {@link android.content.Intent#EXTRA_SHORTCUT_NAME} The text that will be displayed with + * the shortcut.
  • + *
  • {@link android.content.Intent#EXTRA_SHORTCUT_ICON} The shortcut's icon, if provided as a + * bitmap, or {@link android.content.Intent#EXTRA_SHORTCUT_ICON_RESOURCE} if provided as + * a drawable resource.
  • + *
+ * + * If you use a simple drawable resource, note that you must wrapper it using + * {@link android.content.Intent.ShortcutIconResource}, as shown below. This is required so + * that the launcher can access resources that are stored in your application's .apk file. If + * you return a bitmap, such as a thumbnail, you can simply put the bitmap into the extras + * bundle using {@link android.content.Intent#EXTRA_SHORTCUT_ICON}. + * + * The shortcut intent can be any intent that you wish the launcher to send, when the user + * clicks on the shortcut. Typically this will be {@link android.content.Intent#ACTION_VIEW} + * with an appropriate Uri for your content, but any Intent will work here as long as it + * triggers the desired action within your Activity. + * @param profile + */ + private void setupShortcut(VpnProfile profile) { + // First, set up the shortcut intent. For this example, we simply create an intent that + // will bring us directly back to this activity. A more typical implementation would use a + // data Uri in order to display a more specific result, or a custom action in order to + // launch a specific operation. + + Intent shortcutIntent = new Intent(Intent.ACTION_MAIN); + shortcutIntent.setClass(this, LaunchVPN.class); + shortcutIntent.putExtra(LaunchVPN.EXTRA_KEY,profile.getUUID().toString()); + + // Then, set up the container intent (the response to the caller) + + Intent intent = new Intent(); + intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); + intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, profile.getName()); + Parcelable iconResource = Intent.ShortcutIconResource.fromContext( + this, R.drawable.icon); + intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource); + + // Now, return the result to the launcher + + setResult(RESULT_OK, intent); + } + + + @Override + public void onItemClick(AdapterView parent, View view, int position, + long id) { + String profileName = ((TextView) view).getText().toString(); + + VpnProfile profile = mPM.getProfileByName(profileName); + + setupShortcut(profile); + finish(); + } + +} diff --git a/app/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java b/app/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java new file mode 100644 index 00000000..c2d4c599 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java @@ -0,0 +1,82 @@ +package de.blinkt.openvpn.activities; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.*; +import android.os.IBinder; + +import de.blinkt.openvpn.R; +import de.blinkt.openvpn.core.OpenVpnService; +import de.blinkt.openvpn.core.ProfileManager; + +/** + * Created by arne on 13.10.13. + */ +public class DisconnectVPN extends Activity implements DialogInterface.OnClickListener{ + 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 + OpenVpnService.LocalBinder binder = (OpenVpnService.LocalBinder) service; + mService = binder.getService(); + } + + @Override + public void onServiceDisconnected(ComponentName arg0) { + mService =null; + } + + }; + + @Override + protected void onResume() { + super.onResume(); + Intent intent = new Intent(this, OpenVpnService.class); + intent.setAction(OpenVpnService.START_SERVICE); + bindService(intent, mConnection, Context.BIND_AUTO_CREATE); + showDisconnectDialog(); + } + + @Override + protected void onStop() { + super.onStop(); + unbindService(mConnection); + } + + // if (getIntent() !=null && OpenVpnService.DISCONNECT_VPN.equals(getIntent().getAction())) + + // setIntent(null); + + /* + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + setIntent(intent); + } + */ + + private void showDisconnectDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.title_cancel); + builder.setMessage(R.string.cancel_connection_query); + builder.setNegativeButton(android.R.string.no, this); + builder.setPositiveButton(android.R.string.yes,this); + + builder.show(); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + if (which == DialogInterface.BUTTON_POSITIVE) { + ProfileManager.setConntectedVpnProfileDisconnected(this); + if (mService != null && mService.getManagement() != null) + mService.getManagement().stopVPN(); + } + finish(); + } +} diff --git a/app/src/main/java/de/blinkt/openvpn/activities/FileSelect.java b/app/src/main/java/de/blinkt/openvpn/activities/FileSelect.java new file mode 100644 index 00000000..511dc736 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/activities/FileSelect.java @@ -0,0 +1,220 @@ +package de.blinkt.openvpn.activities; + + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; + +import android.app.ActionBar; +import android.app.ActionBar.Tab; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.AlertDialog.Builder; +import android.app.Fragment; +import android.app.FragmentTransaction; +import android.content.Intent; +import android.os.Bundle; +import android.os.Environment; +import android.util.Base64; + +import de.blinkt.openvpn.R; +import de.blinkt.openvpn.VpnProfile; +import de.blinkt.openvpn.fragments.FileSelectionFragment; +import de.blinkt.openvpn.fragments.InlineFileTab; + +public class FileSelect extends Activity { + public static final String RESULT_DATA = "RESULT_PATH"; + public static final String START_DATA = "START_DATA"; + public static final String WINDOW_TITLE = "WINDOW_TILE"; + public static final String NO_INLINE_SELECTION = "de.blinkt.openvpn.NO_INLINE_SELECTION"; + public static final String SHOW_CLEAR_BUTTON = "de.blinkt.openvpn.SHOW_CLEAR_BUTTON"; + public static final String DO_BASE64_ENCODE = "de.blinkt.openvpn.BASE64ENCODE"; + + private FileSelectionFragment mFSFragment; + private InlineFileTab mInlineFragment; + private String mData; + private Tab inlineFileTab; + private Tab fileExplorerTab; + private boolean mNoInline; + private boolean mShowClear; + private boolean mBase64Encode; + + + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.file_dialog); + + mData = getIntent().getStringExtra(START_DATA); + if(mData==null) + mData=Environment.getExternalStorageDirectory().getPath(); + + String title = getIntent().getStringExtra(WINDOW_TITLE); + int titleId = getIntent().getIntExtra(WINDOW_TITLE, 0); + if(titleId!=0) + title =getString(titleId); + if(title!=null) + setTitle(title); + + mNoInline = getIntent().getBooleanExtra(NO_INLINE_SELECTION, false); + mShowClear = getIntent().getBooleanExtra(SHOW_CLEAR_BUTTON, false); + mBase64Encode = getIntent().getBooleanExtra(DO_BASE64_ENCODE, false); + + ActionBar bar = getActionBar(); + bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); + fileExplorerTab = bar.newTab().setText(R.string.file_explorer_tab); + inlineFileTab = bar.newTab().setText(R.string.inline_file_tab); + + mFSFragment = new FileSelectionFragment(); + fileExplorerTab.setTabListener(new MyTabsListener(this, mFSFragment)); + bar.addTab(fileExplorerTab); + + if(!mNoInline) { + mInlineFragment = new InlineFileTab(); + inlineFileTab.setTabListener(new MyTabsListener(this, mInlineFragment)); + bar.addTab(inlineFileTab); + } else { + mFSFragment.setNoInLine(); + } + + + } + + public boolean showClear() { + if(mData == null || mData.equals("")) + return false; + else + return mShowClear; + } + + protected class MyTabsListener implements ActionBar.TabListener + { + private Fragment mFragment; + private boolean mAdded=false; + + public MyTabsListener( Activity activity, Fragment fragment){ + this.mFragment = fragment; + } + + public void onTabSelected(Tab tab, FragmentTransaction ft) { + // Check if the fragment is already initialized + if (!mAdded) { + // If not, instantiate and add it to the activity + ft.add(android.R.id.content, mFragment); + mAdded =true; + } else { + // If it exists, simply attach it in order to show it + ft.attach(mFragment); + } + } + + @Override + public void onTabUnselected(Tab tab, FragmentTransaction ft) { + ft.detach(mFragment); + } + + @Override + public void onTabReselected(Tab tab, FragmentTransaction ft) { + + } + } + + public void importFile(String path) { + File ifile = new File(path); + Exception fe = null; + try { + + String data = ""; + + byte[] fileData = readBytesFromFile(ifile) ; + if(mBase64Encode) + data += Base64.encodeToString(fileData, Base64.DEFAULT); + else + data += new String(fileData); + + mData =data; + + /* + mInlineFragment.setData(data); + getActionBar().selectTab(inlineFileTab); */ + saveInlineData(ifile.getName(), data); + } catch (FileNotFoundException e) { + fe = e; + } catch (IOException e) { + fe =e; + } + if(fe!=null) { + Builder ab = new AlertDialog.Builder(this); + ab.setTitle(R.string.error_importing_file); + ab.setMessage(getString(R.string.import_error_message) + "\n" + fe.getLocalizedMessage()); + ab.setPositiveButton(android.R.string.ok, null); + ab.show(); + } + } + + static private byte[] readBytesFromFile(File file) throws IOException { + InputStream input = new FileInputStream(file); + + long len= file.length(); + if (len > VpnProfile.MAX_EMBED_FILE_SIZE) + throw new IOException("selected file size too big to embed into profile"); + + // Create the byte array to hold the data + byte[] bytes = new byte[(int) len]; + + // Read in the bytes + int offset = 0; + int bytesRead = 0; + while (offset < bytes.length + && (bytesRead=input.read(bytes, offset, bytes.length-offset)) >= 0) { + offset += bytesRead; + } + + input.close(); + return bytes; + } + + + public void setFile(String path) { + Intent intent = new Intent(); + intent.putExtra(RESULT_DATA, path); + setResult(Activity.RESULT_OK,intent); + finish(); + } + + public String getSelectPath() { + if(VpnProfile.isEmbedded(mData)) + return mData; + else + return Environment.getExternalStorageDirectory().getPath(); + } + + public CharSequence getInlineData() { + if(VpnProfile.isEmbedded(mData)) + return VpnProfile.getEmbeddedContent(mData); + else + return ""; + } + + public void clearData() { + Intent intent = new Intent(); + intent.putExtra(RESULT_DATA, (String)null); + setResult(Activity.RESULT_OK,intent); + finish(); + + } + + public void saveInlineData(String fileName, String string) { + Intent intent = new Intent(); + + if(fileName==null) + intent.putExtra(RESULT_DATA, VpnProfile.INLINE_TAG + string); + else + intent.putExtra(RESULT_DATA,VpnProfile.DISPLAYNAME_TAG + fileName + VpnProfile.INLINE_TAG + string); + setResult(Activity.RESULT_OK, intent); + finish(); + + } +} diff --git a/app/src/main/java/de/blinkt/openvpn/activities/LogWindow.java b/app/src/main/java/de/blinkt/openvpn/activities/LogWindow.java new file mode 100644 index 00000000..27197035 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/activities/LogWindow.java @@ -0,0 +1,32 @@ +package de.blinkt.openvpn.activities; + +import android.app.Activity; +import android.os.Bundle; +import android.view.MenuItem; + +import de.blinkt.openvpn.R; +import de.blinkt.openvpn.fragments.LogFragment; + +/** + * Created by arne on 13.10.13. + */ +public class LogWindow extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.log_window); + getActionBar().setDisplayHomeAsUpEnabled(true); + + if (savedInstanceState == null) { + getFragmentManager().beginTransaction() + .add(R.id.container, new LogFragment()) + .commit(); + } + + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + return super.onOptionsItemSelected(item); + } +} diff --git a/app/src/main/java/de/blinkt/openvpn/activities/MainActivity.java b/app/src/main/java/de/blinkt/openvpn/activities/MainActivity.java new file mode 100644 index 00000000..b32c80cc --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/activities/MainActivity.java @@ -0,0 +1,103 @@ +package de.blinkt.openvpn.activities; + +import android.app.ActionBar; +import android.app.ActionBar.Tab; +import android.app.Activity; +import android.app.Fragment; +import android.app.FragmentTransaction; +import android.content.Intent; + +import de.blinkt.openvpn.R; +import de.blinkt.openvpn.fragments.*; + + +public class MainActivity extends Activity { + + protected void onCreate(android.os.Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ActionBar bar = getActionBar(); + bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); + + Tab vpnListTab = bar.newTab().setText(R.string.vpn_list_title); + Tab generalTab = bar.newTab().setText(R.string.generalsettings); + Tab faqtab = bar.newTab().setText(R.string.faq); + Tab abouttab = bar.newTab().setText(R.string.about); + + vpnListTab.setTabListener(new TabListener("profiles", VPNProfileList.class)); + generalTab.setTabListener(new TabListener("settings", GeneralSettings.class)); + faqtab.setTabListener(new TabListener("faq", FaqFragment.class)); + abouttab.setTabListener(new TabListener("about", AboutFragment.class)); + + bar.addTab(vpnListTab); + bar.addTab(generalTab); + bar.addTab(faqtab); + bar.addTab(abouttab); + + if (false) { + Tab logtab = bar.newTab().setText("Log"); + logtab.setTabListener(new TabListener("log", LogFragment.class)); + bar.addTab(logtab); + } + + if(SendDumpFragment.getLastestDump(this)!=null) { + Tab sendDump = bar.newTab().setText(R.string.crashdump); + sendDump.setTabListener(new TabListener("crashdump",SendDumpFragment.class)); + bar.addTab(sendDump); + } + + } + + protected class TabListener implements ActionBar.TabListener + { + private Fragment mFragment; + private String mTag; + private Class mClass; + + public TabListener(String tag, Class clz) { + mTag = tag; + mClass = clz; + + // Check to see if we already have a fragment for this tab, probably + // from a previously saved state. If so, deactivate it, because our + // initial state is that a tab isn't shown. + mFragment = getFragmentManager().findFragmentByTag(mTag); + if (mFragment != null && !mFragment.isDetached()) { + FragmentTransaction ft = getFragmentManager().beginTransaction(); + ft.detach(mFragment); + ft.commit(); + } + } + + public void onTabSelected(Tab tab, FragmentTransaction ft) { + if (mFragment == null) { + mFragment = Fragment.instantiate(MainActivity.this, mClass.getName()); + ft.add(android.R.id.content, mFragment, mTag); + } else { + ft.attach(mFragment); + } + } + + public void onTabUnselected(Tab tab, FragmentTransaction ft) { + if (mFragment != null) { + ft.detach(mFragment); + } + } + + + @Override + public void onTabReselected(Tab tab, FragmentTransaction ft) { + + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + System.out.println(data); + + + } + + +} diff --git a/app/src/main/java/de/blinkt/openvpn/activities/VPNPreferences.java b/app/src/main/java/de/blinkt/openvpn/activities/VPNPreferences.java new file mode 100644 index 00000000..1ebb16b2 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/activities/VPNPreferences.java @@ -0,0 +1,165 @@ +package de.blinkt.openvpn.activities; + +import java.util.List; + +import android.annotation.TargetApi; +import android.app.AlertDialog; +import android.app.Fragment; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.preference.PreferenceActivity; +import android.preference.PreferenceFragment; +import android.view.Menu; +import android.view.MenuItem; + +import de.blinkt.openvpn.R; +import de.blinkt.openvpn.VpnProfile; +import de.blinkt.openvpn.core.ProfileManager; +import de.blinkt.openvpn.fragments.Settings_Authentication; +import de.blinkt.openvpn.fragments.Settings_Basic; +import de.blinkt.openvpn.fragments.Settings_IP; +import de.blinkt.openvpn.fragments.Settings_Obscure; +import de.blinkt.openvpn.fragments.Settings_Routing; +import de.blinkt.openvpn.fragments.ShowConfigFragment; +import de.blinkt.openvpn.fragments.VPNProfileList; + + +public class VPNPreferences extends PreferenceActivity { + + static final Class validFragments[] = new Class[] { + Settings_Authentication.class, Settings_Basic.class, Settings_IP.class, + Settings_Obscure.class, Settings_Routing.class, ShowConfigFragment.class + }; + + private String mProfileUUID; + private VpnProfile mProfile; + + public VPNPreferences() { + super(); + } + + + @TargetApi(Build.VERSION_CODES.KITKAT) + @Override + protected boolean isValidFragment(String fragmentName) { + for (Class c: validFragments) + if (c.getName().equals(fragmentName)) + return true; + return false; + + } + + @Override + protected void onStop() { + super.onStop(); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + outState.putString(getIntent().getStringExtra(getPackageName() + ".profileUUID"),mProfileUUID); + super.onSaveInstanceState(outState); + } + + @Override + protected void onResume() { + super.onResume(); + Intent intent = getIntent(); + + + if(intent!=null) { + String profileUUID = intent.getStringExtra(getPackageName() + ".profileUUID"); + if(profileUUID==null) { + Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS); + profileUUID = initialArguments.getString(getPackageName() + ".profileUUID"); + } + if(profileUUID!=null){ + + mProfileUUID = profileUUID; + mProfile = ProfileManager.get(this,mProfileUUID); + + } + } + // When a profile is deleted from a category fragment in hadset mod we need to finish + // this activity as well when returning + if (mProfile==null || mProfile.profileDleted) { + setResult(VPNProfileList.RESULT_VPN_DELETED); + finish(); + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + mProfileUUID = getIntent().getStringExtra(getPackageName() + ".profileUUID"); + if(savedInstanceState!=null){ + String savedUUID = savedInstanceState.getString(getPackageName() + ".profileUUID"); + if(savedUUID!=null) + mProfileUUID=savedUUID; + } + + mProfile = ProfileManager.get(this,mProfileUUID); + if(mProfile!=null) { + setTitle(getString(R.string.edit_profile_title, mProfile.getName())); + } + super.onCreate(savedInstanceState); + } + + + + @Override + public void onBuildHeaders(List
target) { + loadHeadersFromResource(R.xml.vpn_headers, target); + for (Header header : target) { + if(header.fragmentArguments==null) + header.fragmentArguments = new Bundle(); + header.fragmentArguments.putString(getPackageName() + ".profileUUID",mProfileUUID); + } + } + + @Override + public void onBackPressed() { + setResult(RESULT_OK, getIntent()); + super.onBackPressed(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if(item.getItemId() == R.id.remove_vpn) + askProfileRemoval(); + return super.onOptionsItemSelected(item); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + + getMenuInflater().inflate(R.menu.vpnpreferences_menu, menu); + + return super.onCreateOptionsMenu(menu); + } + + private void askProfileRemoval() { + AlertDialog.Builder dialog = new AlertDialog.Builder(this); + dialog.setTitle("Confirm deletion"); + dialog.setMessage(getString(R.string.remove_vpn_query, mProfile.mName)); + + dialog.setPositiveButton(android.R.string.yes, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + removeProfile(mProfile); + } + + }); + dialog.setNegativeButton(android.R.string.no,null); + dialog.create().show(); + } + + protected void removeProfile(VpnProfile profile) { + ProfileManager.getInstance(this).removeProfile(this,profile); + setResult(VPNProfileList.RESULT_VPN_DELETED); + finish(); + + } +} + diff --git a/app/src/main/java/de/blinkt/openvpn/api/APIVpnProfile.java b/app/src/main/java/de/blinkt/openvpn/api/APIVpnProfile.java new file mode 100644 index 00000000..f5591764 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/api/APIVpnProfile.java @@ -0,0 +1,51 @@ +package de.blinkt.openvpn.api; + +import android.os.Parcel; +import android.os.Parcelable; + +public class APIVpnProfile implements Parcelable { + + public final String mUUID; + public final String mName; + public final boolean mUserEditable; + + public APIVpnProfile(Parcel in) { + mUUID = in.readString(); + mName = in.readString(); + mUserEditable = in.readInt() != 0; + } + + public APIVpnProfile(String uuidString, String name, boolean userEditable) { + mUUID=uuidString; + mName = name; + mUserEditable=userEditable; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mUUID); + dest.writeString(mName); + if(mUserEditable) + dest.writeInt(0); + else + dest.writeInt(1); + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public APIVpnProfile createFromParcel(Parcel in) { + return new APIVpnProfile(in); + } + + public APIVpnProfile[] newArray(int size) { + return new APIVpnProfile[size]; + } + }; + + +} diff --git a/app/src/main/java/de/blinkt/openvpn/api/ConfirmDialog.java b/app/src/main/java/de/blinkt/openvpn/api/ConfirmDialog.java new file mode 100644 index 00000000..bcab79ed --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/api/ConfirmDialog.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.blinkt.openvpn.api; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.AlertDialog.Builder; +import android.content.DialogInterface; +import android.content.DialogInterface.OnShowListener; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.CompoundButton; +import android.widget.ImageView; +import android.widget.TextView; +import de.blinkt.openvpn.R; + + +public class ConfirmDialog extends Activity implements +CompoundButton.OnCheckedChangeListener, DialogInterface.OnClickListener { + private static final String TAG = "OpenVPNVpnConfirm"; + + private String mPackage; + + private Button mButton; + + private AlertDialog mAlert; + + @Override + protected void onResume() { + super.onResume(); + try { + mPackage = getCallingPackage(); + if (mPackage==null) { + finish(); + return; + } + + + PackageManager pm = getPackageManager(); + ApplicationInfo app = pm.getApplicationInfo(mPackage, 0); + + View view = View.inflate(this, R.layout.api_confirm, null); + ((ImageView) view.findViewById(R.id.icon)).setImageDrawable(app.loadIcon(pm)); + ((TextView) view.findViewById(R.id.prompt)).setText( + getString(R.string.prompt, app.loadLabel(pm), getString(R.string.app))); + ((CompoundButton) view.findViewById(R.id.check)).setOnCheckedChangeListener(this); + + + Builder builder = new AlertDialog.Builder(this); + + builder.setView(view); + + builder.setIconAttribute(android.R.attr.alertDialogIcon); + builder.setTitle(android.R.string.dialog_alert_title); + builder.setPositiveButton(android.R.string.ok,this); + builder.setNegativeButton(android.R.string.cancel,this); + + mAlert = builder.create(); + mAlert.setCanceledOnTouchOutside(false); + + mAlert.setOnShowListener (new OnShowListener() { + + @Override + public void onShow(DialogInterface dialog) { + mButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE); + mButton.setEnabled(false); + + } + }); + + //setCloseOnTouchOutside(false); + + mAlert.show(); + + } catch (Exception e) { + Log.e(TAG, "onResume", e); + finish(); + } + } + + @Override + public void onBackPressed() { + setResult(RESULT_CANCELED); + finish(); + } + + @Override + public void onCheckedChanged(CompoundButton button, boolean checked) { + mButton.setEnabled(checked); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + + if (which == DialogInterface.BUTTON_POSITIVE) { + ExternalAppDatabase extapps = new ExternalAppDatabase(this); + extapps.addApp(mPackage); + setResult(RESULT_OK); + finish(); + } + + if (which == DialogInterface.BUTTON_NEGATIVE) { + setResult(RESULT_CANCELED); + finish(); + } + } + +} + diff --git a/app/src/main/java/de/blinkt/openvpn/api/ExternalAppDatabase.java b/app/src/main/java/de/blinkt/openvpn/api/ExternalAppDatabase.java new file mode 100644 index 00000000..02c369b1 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/api/ExternalAppDatabase.java @@ -0,0 +1,57 @@ +package de.blinkt.openvpn.api; + +import java.util.HashSet; +import java.util.Set; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.preference.PreferenceManager; + +public class ExternalAppDatabase { + + Context mContext; + + public ExternalAppDatabase(Context c) { + mContext =c; + } + + private final String PREFERENCES_KEY = "PREFERENCES_KEY"; + + boolean isAllowed(String packagename) { + Set allowedapps = getExtAppList(); + + return allowedapps.contains(packagename); + + } + + public Set getExtAppList() { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext); + return prefs.getStringSet(PREFERENCES_KEY, new HashSet()); + } + + void addApp(String packagename) + { + Set allowedapps = getExtAppList(); + allowedapps.add(packagename); + saveExtAppList(allowedapps); + } + + private void saveExtAppList( Set allowedapps) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext); + Editor prefedit = prefs.edit(); + prefedit.putStringSet(PREFERENCES_KEY, allowedapps); + prefedit.apply(); + } + + public void clearAllApiApps() { + saveExtAppList(new HashSet()); + } + + public void removeApp(String packagename) { + Set allowedapps = getExtAppList(); + allowedapps.remove(packagename); + saveExtAppList(allowedapps); + } + +} diff --git a/app/src/main/java/de/blinkt/openvpn/api/ExternalOpenVPNService.java b/app/src/main/java/de/blinkt/openvpn/api/ExternalOpenVPNService.java new file mode 100644 index 00000000..928a85eb --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/api/ExternalOpenVPNService.java @@ -0,0 +1,317 @@ +package de.blinkt.openvpn.api; + +import java.io.IOException; +import java.io.StringReader; +import java.lang.ref.WeakReference; +import java.util.LinkedList; +import java.util.List; + +import android.annotation.TargetApi; +import android.app.Service; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.net.VpnService; +import android.os.*; +import de.blinkt.openvpn.R; +import de.blinkt.openvpn.VpnProfile; +import de.blinkt.openvpn.core.ConfigParser; +import de.blinkt.openvpn.core.ConfigParser.ConfigParseError; +import de.blinkt.openvpn.core.VpnStatus; +import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus; +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 de.blinkt.openvpn.core.VPNLaunchHelper; + +@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) +public class ExternalOpenVPNService extends Service implements StateListener { + + private static final int SEND_TOALL = 0; + + final RemoteCallbackList mCallbacks = + new RemoteCallbackList(); + + private OpenVpnService mService; + private ExternalAppDatabase mExtAppDb; + + + 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; + } + + }; + + @Override + public void onCreate() { + super.onCreate(); + VpnStatus.addStateListener(this); + mExtAppDb = new ExternalAppDatabase(this); + + Intent intent = new Intent(getBaseContext(), OpenVpnService.class); + intent.setAction(OpenVpnService.START_SERVICE); + + bindService(intent, mConnection, Context.BIND_AUTO_CREATE); + mHandler.setService(this); + } + + private final IOpenVPNAPIService.Stub mBinder = new IOpenVPNAPIService.Stub() { + + private void checkOpenVPNPermission() throws SecurityRemoteException { + PackageManager pm = getPackageManager(); + + for (String apppackage : mExtAppDb.getExtAppList()) { + ApplicationInfo app; + try { + app = pm.getApplicationInfo(apppackage, 0); + if (Binder.getCallingUid() == app.uid) { + return; + } + } catch (NameNotFoundException e) { + // App not found. Remove it from the list + mExtAppDb.removeApp(apppackage); + } + + } + throw new SecurityException("Unauthorized OpenVPN API Caller"); + } + + @Override + public List getProfiles() throws RemoteException { + checkOpenVPNPermission(); + + ProfileManager pm = ProfileManager.getInstance(getBaseContext()); + + List profiles = new LinkedList(); + + for (VpnProfile vp : pm.getProfiles()) + profiles.add(new APIVpnProfile(vp.getUUIDString(), vp.mName, vp.mUserEditable)); + + return profiles; + } + + @Override + public void startProfile(String profileUUID) throws RemoteException { + checkOpenVPNPermission(); + + Intent shortVPNIntent = new Intent(Intent.ACTION_MAIN); + shortVPNIntent.setClass(getBaseContext(), de.blinkt.openvpn.LaunchVPN.class); + shortVPNIntent.putExtra(de.blinkt.openvpn.LaunchVPN.EXTRA_KEY, profileUUID); + shortVPNIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(shortVPNIntent); + } + + public void startVPN(String inlineconfig) throws RemoteException { + checkOpenVPNPermission(); + + ConfigParser cp = new ConfigParser(); + try { + cp.parseConfig(new StringReader(inlineconfig)); + VpnProfile vp = cp.convertProfile(); + if (vp.checkProfile(getApplicationContext()) != R.string.no_error_found) + throw new RemoteException(getString(vp.checkProfile(getApplicationContext()))); + + + ProfileManager.setTemporaryProfile(vp); + VPNLaunchHelper.startOpenVpn(vp, getBaseContext()); + + + } catch (IOException e) { + throw new RemoteException(e.getMessage()); + } catch (ConfigParseError e) { + throw new RemoteException(e.getMessage()); + } + } + + @Override + public boolean addVPNProfile(String name, String config) throws RemoteException { + checkOpenVPNPermission(); + + ConfigParser cp = new ConfigParser(); + try { + cp.parseConfig(new StringReader(config)); + VpnProfile vp = cp.convertProfile(); + vp.mName = name; + ProfileManager pm = ProfileManager.getInstance(getBaseContext()); + pm.addProfile(vp); + } catch (IOException e) { + VpnStatus.logException(e); + return false; + } catch (ConfigParseError e) { + VpnStatus.logException(e); + return false; + } + + return true; + } + + + @Override + public Intent prepare(String packagename) { + if (new ExternalAppDatabase(ExternalOpenVPNService.this).isAllowed(packagename)) + return null; + + Intent intent = new Intent(); + intent.setClass(ExternalOpenVPNService.this, ConfirmDialog.class); + return intent; + } + + @Override + public Intent prepareVPNService() throws RemoteException { + checkOpenVPNPermission(); + + if (VpnService.prepare(ExternalOpenVPNService.this) == null) + return null; + else + return new Intent(getBaseContext(), GrantPermissionsActivity.class); + } + + + @Override + public void registerStatusCallback(IOpenVPNStatusCallback cb) + throws RemoteException { + checkOpenVPNPermission(); + + if (cb != null) { + cb.newStatus(mMostRecentState.vpnUUID, mMostRecentState.state, + mMostRecentState.logmessage, mMostRecentState.level.name()); + mCallbacks.register(cb); + } + + + } + + @Override + public void unregisterStatusCallback(IOpenVPNStatusCallback cb) + throws RemoteException { + checkOpenVPNPermission(); + + if (cb != null) + mCallbacks.unregister(cb); + } + + @Override + public void disconnect() throws RemoteException { + checkOpenVPNPermission(); + if (mService != null && mService.getManagement() != null) + mService.getManagement().stopVPN(); + } + + @Override + public void pause() throws RemoteException { + checkOpenVPNPermission(); + if (mService != null) + mService.userPause(true); + } + + @Override + public void resume() throws RemoteException { + checkOpenVPNPermission(); + if (mService != null) + mService.userPause(false); + + } + }; + + + private UpdateMessage mMostRecentState; + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + @Override + public void onDestroy() { + super.onDestroy(); + mCallbacks.kill(); + unbindService(mConnection); + VpnStatus.removeStateListener(this); + } + + class UpdateMessage { + public String state; + public String logmessage; + public ConnectionStatus level; + public String vpnUUID; + + public UpdateMessage(String state, String logmessage, ConnectionStatus level) { + this.state = state; + this.logmessage = logmessage; + this.level = level; + } + } + + @Override + public void updateState(String state, String logmessage, int resid, ConnectionStatus level) { + mMostRecentState = new UpdateMessage(state, logmessage, level); + if (ProfileManager.getLastConnectedVpn() != null) + mMostRecentState.vpnUUID = ProfileManager.getLastConnectedVpn().getUUIDString(); + + Message msg = mHandler.obtainMessage(SEND_TOALL, mMostRecentState); + msg.sendToTarget(); + + } + + private static final OpenVPNServiceHandler mHandler = new OpenVPNServiceHandler(); + + + static class OpenVPNServiceHandler extends Handler { + WeakReference service = null; + + private void setService(ExternalOpenVPNService eos) { + service = new WeakReference(eos); + } + + @Override + public void handleMessage(Message msg) { + + RemoteCallbackList callbacks; + switch (msg.what) { + case SEND_TOALL: + if (service == null || service.get() == null) + return; + + callbacks = service.get().mCallbacks; + + + // Broadcast to all clients the new value. + final int N = callbacks.beginBroadcast(); + for (int i = 0; i < N; i++) { + try { + sendUpdate(callbacks.getBroadcastItem(i), (UpdateMessage) msg.obj); + } catch (RemoteException e) { + // The RemoteCallbackList will take care of removing + // the dead object for us. + } + } + callbacks.finishBroadcast(); + break; + } + } + + private void sendUpdate(IOpenVPNStatusCallback broadcastItem, + UpdateMessage um) throws RemoteException { + broadcastItem.newStatus(um.vpnUUID, um.state, um.logmessage, um.level.name()); + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/de/blinkt/openvpn/api/SecurityRemoteException.java b/app/src/main/java/de/blinkt/openvpn/api/SecurityRemoteException.java new file mode 100644 index 00000000..e6011aa3 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/api/SecurityRemoteException.java @@ -0,0 +1,12 @@ +package de.blinkt.openvpn.api; + +import android.os.RemoteException; + +public class SecurityRemoteException extends RemoteException { + + /** + * + */ + private static final long serialVersionUID = 1L; + +} diff --git a/app/src/main/java/de/blinkt/openvpn/core/CIDRIP.java b/app/src/main/java/de/blinkt/openvpn/core/CIDRIP.java new file mode 100644 index 00000000..960e7d11 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/core/CIDRIP.java @@ -0,0 +1,70 @@ +package de.blinkt.openvpn.core; + +import java.util.Locale; + +class CIDRIP { + String mIp; + int len; + + + public CIDRIP(String ip, String mask) { + mIp = ip; + long netmask = getInt(mask); + + // Add 33. bit to ensure the loop terminates + netmask += 1l << 32; + + int lenZeros = 0; + while ((netmask & 0x1) == 0) { + lenZeros++; + netmask = netmask >> 1; + } + // Check if rest of netmask is only 1s + if (netmask != (0x1ffffffffl >> lenZeros)) { + // Asume no CIDR, set /32 + len = 32; + } else { + len = 32 - lenZeros; + } + + } + + public CIDRIP(String address, int prefix_length) { + len = prefix_length; + mIp = address; + } + + @Override + public String toString() { + return String.format(Locale.ENGLISH, "%s/%d", mIp, len); + } + + public boolean normalise() { + long ip = getInt(mIp); + + long newip = ip & (0xffffffffl << (32 - len)); + if (newip != ip) { + mIp = String.format("%d.%d.%d.%d", (newip & 0xff000000) >> 24, (newip & 0xff0000) >> 16, (newip & 0xff00) >> 8, newip & 0xff); + return true; + } else { + return false; + } + } + + static long getInt(String ipaddr) { + String[] ipt = ipaddr.split("\\."); + long ip = 0; + + ip += Long.parseLong(ipt[0]) << 24; + ip += Integer.parseInt(ipt[1]) << 16; + ip += Integer.parseInt(ipt[2]) << 8; + ip += Integer.parseInt(ipt[3]); + + return ip; + } + + public long getInt() { + return getInt(mIp); + } + +} \ No newline at end of file diff --git a/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java b/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java new file mode 100644 index 00000000..378b6b92 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java @@ -0,0 +1,767 @@ +package de.blinkt.openvpn.core; + +import de.blinkt.openvpn.VpnProfile; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; +import java.util.Collections; +import java.util.HashMap; +import java.util.Locale; +import java.util.Vector; + +//! Openvpn Config FIle Parser, probably not 100% accurate but close enough + +// And remember, this is valid :) +// -- +// bar +// +public class ConfigParser { + + + public static final String CONVERTED_PROFILE = "converted Profile"; + private HashMap>> options = new HashMap>>(); + private HashMap> meta = new HashMap>(); + + + private boolean extraRemotesAsCustom=false; + + public void parseConfig(Reader reader) throws IOException, ConfigParseError { + + + BufferedReader br =new BufferedReader(reader); + + while (true){ + String line = br.readLine(); + if(line==null) + break; + + // Check for OpenVPN Access Server Meta information + if (line.startsWith("# OVPN_ACCESS_SERVER_")) { + Vector metaarg = parsemeta(line); + meta.put(metaarg.get(0),metaarg); + continue; + } + Vector args = parseline(line); + + if(args.size() ==0) + continue; + + + if(args.get(0).startsWith("--")) + args.set(0, args.get(0).substring(2)); + + checkinlinefile(args,br); + + String optionname = args.get(0); + if(!options.containsKey(optionname)) { + options.put(optionname, new Vector>()); + } + options.get(optionname).add(args); + } + } + + private Vector parsemeta(String line) { + String meta = line.split("#\\sOVPN_ACCESS_SERVER_", 2)[1]; + String[] parts = meta.split("=",2); + Vector rval = new Vector(); + Collections.addAll(rval, parts); + return rval; + + } + + private void checkinlinefile(Vector args, BufferedReader br) throws IOException, ConfigParseError { + String arg0 = args.get(0).trim(); + // CHeck for + if(arg0.startsWith("<") && arg0.endsWith(">")) { + String argname = arg0.substring(1, arg0.length()-1); + String inlinefile = VpnProfile.INLINE_TAG; + + String endtag = String.format("",argname); + do { + String line = br.readLine(); + if(line==null){ + throw new ConfigParseError(String.format("No endtag for starttag <%s> found",argname,argname)); + } + if(line.trim().equals(endtag)) + break; + else { + inlinefile+=line; + inlinefile+= "\n"; + } + } while(true); + + args.clear(); + args.add(argname); + args.add(inlinefile); + } + + } + + enum linestate { + initial, + readin_single_quote + , reading_quoted, reading_unquoted, done} + + private boolean space(char c) { + // I really hope nobody is using zero bytes inside his/her config file + // to sperate parameter but here we go: + return Character.isWhitespace(c) || c == '\0'; + + } + + public class ConfigParseError extends Exception { + private static final long serialVersionUID = -60L; + + public ConfigParseError(String msg) { + super(msg); + } + } + + + // adapted openvpn's parse function to java + private Vector parseline(String line) throws ConfigParseError { + Vector parameters = new Vector(); + + if (line.length()==0) + return parameters; + + + linestate state = linestate.initial; + boolean backslash = false; + char out=0; + + int pos=0; + String currentarg=""; + + do { + // Emulate the c parsing ... + char in; + if(pos < line.length()) + in = line.charAt(pos); + else + in = '\0'; + + if (!backslash && in == '\\' && state != linestate.readin_single_quote) + { + backslash = true; + } + else + { + if (state == linestate.initial) + { + if (!space (in)) + { + if (in == ';' || in == '#') /* comment */ + break; + if (!backslash && in == '\"') + state = linestate.reading_quoted; + else if (!backslash && in == '\'') + state = linestate.readin_single_quote; + else + { + out = in; + state = linestate.reading_unquoted; + } + } + } + else if (state == linestate.reading_unquoted) + { + if (!backslash && space (in)) + state = linestate.done; + else + out = in; + } + else if (state == linestate.reading_quoted) + { + if (!backslash && in == '\"') + state = linestate.done; + else + out = in; + } + else if (state == linestate.readin_single_quote) + { + if (in == '\'') + state = linestate.done; + else + out = in; + } + + if (state == linestate.done) + { + /* ASSERT (parm_len > 0); */ + state = linestate.initial; + parameters.add(currentarg); + currentarg = ""; + out =0; + } + + if (backslash && out!=0) + { + if (!(out == '\\' || out == '\"' || space (out))) + { + throw new ConfigParseError("Options warning: Bad backslash ('\\') usage"); + } + } + backslash = false; + } + + /* store parameter character */ + if (out!=0) + { + currentarg+=out; + } + } while (pos++ < line.length()); + + return parameters; + } + + + final String[] unsupportedOptions = { "config", + "connection", + "proto-force", + "remote-random", + "tls-server" + + }; + + // Ignore all scripts + // in most cases these won't work and user who wish to execute scripts will + // figure out themselves + final String[] ignoreOptions = { "tls-client", + "askpass", + "auth-nocache", + "up", + "down", + "route-up", + "ipchange", + "route-up", + "route-pre-down", + "auth-user-pass-verify", + "dhcp-release", + "dhcp-renew", + "dh", + "group", + "ip-win32", + "management-hold", + "management", + "management-client", + "management-query-remote", + "management-query-passwords", + "management-query-proxy", + "management-external-key", + "management-forget-disconnect", + "management-signal", + "management-log-cache", + "management-up-down", + "management-client-user", + "management-client-group", + "pause-exit", + "plugin", + "machine-readable-output", + "persist-key", + "register-dns", + "route-delay", + "route-gateway", + "route-metric", + "route-method", + "status", + "script-security", + "show-net-up", + "suppress-timestamps", + "tmp-dir", + "tun-ipv6", + "topology", + "user", + "win-sys", + + }; + + final String[][] ignoreOptionsWithArg = + { + {"setenv", "IV_GUI_VER"}, + {"setenv", "IV_OPENVPN_GUI_VERSION"} + }; + + final String[] connectionOptions = { + "local", + "remote", + "float", + "port", +// "connect-retry", + "connect-timeout", + "connect-retry-max", + "link-mtu", + "tun-mtu", + "tun-mtu-extra", + "fragment", + "mtu-disc", + "local-port", + "remote-port", + "bind", + "nobind", + "proto", + "http-proxy", + "http-proxy-retry", + "http-proxy-timeout", + "http-proxy-option", + "socks-proxy", + "socks-proxy-retry", + "explicit-exit-notify", + "mssfix" + }; + + + // This method is far too long + @SuppressWarnings("ConstantConditions") + public VpnProfile convertProfile() throws ConfigParseError{ + boolean noauthtypeset=true; + VpnProfile np = new VpnProfile(CONVERTED_PROFILE); + // Pull, client, tls-client + np.clearDefaults(); + + if(options.containsKey("client") || options.containsKey("pull")) { + np.mUsePull=true; + options.remove("pull"); + options.remove("client"); + } + + Vector secret = getOption("secret", 1, 2); + if(secret!=null) + { + np.mAuthenticationType=VpnProfile.TYPE_STATICKEYS; + noauthtypeset=false; + np.mUseTLSAuth=true; + np.mTLSAuthFilename=secret.get(1); + if(secret.size()==3) + np.mTLSAuthDirection=secret.get(2); + + } + + Vector> routes = getAllOption("route", 1, 4); + if(routes!=null) { + String routeopt = ""; + String routeExcluded = ""; + for(Vector route:routes){ + String netmask = "255.255.255.255"; + String gateway = "vpn_gateway"; + + if(route.size() >= 3) + netmask = route.get(2); + if (route.size() >= 4) + gateway = route.get(3); + + String net = route.get(1); + try { + CIDRIP cidr = new CIDRIP(net, netmask); + if (gateway.equals("net_gateway")) + routeExcluded += cidr.toString() + " "; + else + routeopt+=cidr.toString() + " "; + } catch (ArrayIndexOutOfBoundsException aioob) { + throw new ConfigParseError("Could not parse netmask of route " + netmask); + } catch (NumberFormatException ne) { + + + + + throw new ConfigParseError("Could not parse netmask of route " + netmask); + } + + } + np.mCustomRoutes=routeopt; + np.mExcludedRoutes=routeExcluded; + } + + Vector> routesV6 = getAllOption("route-ipv6", 1, 4); + if (routesV6!=null) { + String customIPv6Routes = ""; + for (Vector route:routesV6){ + customIPv6Routes += route.get(1) + " "; + } + + np.mCustomRoutesv6 = customIPv6Routes; + } + + // Also recognize tls-auth [inline] direction ... + Vector> tlsauthoptions = getAllOption("tls-auth", 1, 2); + if(tlsauthoptions!=null) { + for(Vector tlsauth:tlsauthoptions) { + if(tlsauth!=null) + { + if(!tlsauth.get(1).equals("[inline]")) { + np.mTLSAuthFilename=tlsauth.get(1); + np.mUseTLSAuth=true; + } + if(tlsauth.size()==3) + np.mTLSAuthDirection=tlsauth.get(2); + } + } + } + + Vector direction = getOption("key-direction", 1, 1); + if(direction!=null) + np.mTLSAuthDirection=direction.get(1); + + Vector> defgw = getAllOption("redirect-gateway", 0, 5); + if(defgw != null) + { + np.mUseDefaultRoute=true; + checkRedirectParameters(np, defgw); + } + + Vector> redirectPrivate = getAllOption("redirect-private",0,5); + if (redirectPrivate != null) + { + checkRedirectParameters(np,redirectPrivate); + } + Vector dev =getOption("dev",1,1); + Vector devtype =getOption("dev-type",1,1); + + if ((devtype != null && devtype.get(1).equals("tun")) || + (dev != null && dev.get(1).startsWith("tun")) || + (devtype == null && dev == null)) { + //everything okay + } else { + throw new ConfigParseError("Sorry. Only tun mode is supported. See the FAQ for more detail"); + } + + + + Vector mode =getOption("mode",1,1); + if (mode != null){ + if(!mode.get(1).equals("p2p")) + throw new ConfigParseError("Invalid mode for --mode specified, need p2p"); + } + + Vector port = getOption("port", 1,1); + if(port!=null){ + np.mServerPort = port.get(1); + } + + Vector rport = getOption("rport", 1,1); + if(port!=null){ + np.mServerPort = port.get(1); + } + + Vector proto = getOption("proto", 1,1); + if(proto!=null){ + np.mUseUdp=isUdpProto(proto.get(1)); + } + + // Parse remote config + Vector> remotes = getAllOption("remote",1,3); + + if(remotes!=null && remotes.size()>=1 ) { + Vector remote = remotes.get(0); + switch (remote.size()) { + case 4: + np.mUseUdp=isUdpProto(remote.get(3)); + case 3: + np.mServerPort = remote.get(2); + case 2: + np.mServerName = remote.get(1); + } + } + + + + Vector> dhcpoptions = getAllOption("dhcp-option", 2, 2); + if(dhcpoptions!=null) { + for(Vector dhcpoption:dhcpoptions) { + String type=dhcpoption.get(1); + String arg = dhcpoption.get(2); + if(type.equals("DOMAIN")) { + np.mSearchDomain=dhcpoption.get(2); + } else if(type.equals("DNS")) { + np.mOverrideDNS=true; + if(np.mDNS1.equals(VpnProfile.DEFAULT_DNS1)) + np.mDNS1=arg; + else + np.mDNS2=arg; + } + } + } + + Vector ifconfig = getOption("ifconfig", 2, 2); + if(ifconfig!=null) { + try { + CIDRIP cidr = new CIDRIP(ifconfig.get(1), ifconfig.get(2)); + np.mIPv4Address=cidr.toString(); + } catch (NumberFormatException nfe) { + throw new ConfigParseError("Could not pase ifconfig IP address: " + nfe.getLocalizedMessage()); + } + + } + + if(getOption("remote-random-hostname", 0, 0)!=null) + np.mUseRandomHostname=true; + + if(getOption("float", 0, 0)!=null) + np.mUseFloat=true; + + if(getOption("comp-lzo", 0, 1)!=null) + np.mUseLzo=true; + + Vector cipher = getOption("cipher", 1, 1); + if(cipher!=null) + np.mCipher= cipher.get(1); + + Vector auth = getOption("auth", 1, 1); + if(auth!=null) + np.mAuth = auth.get(1); + + + Vector ca = getOption("ca",1,1); + if(ca!=null){ + np.mCaFilename = ca.get(1); + } + + Vector cert = getOption("cert",1,1); + if(cert!=null){ + np.mClientCertFilename = cert.get(1); + np.mAuthenticationType = VpnProfile.TYPE_CERTIFICATES; + noauthtypeset=false; + } + Vector key= getOption("key",1,1); + if(key!=null) + np.mClientKeyFilename=key.get(1); + + Vector pkcs12 = getOption("pkcs12",1,1); + if(pkcs12!=null) { + np.mPKCS12Filename = pkcs12.get(1); + np.mAuthenticationType = VpnProfile.TYPE_KEYSTORE; + noauthtypeset=false; + } + + + Vector compatnames = getOption("compat-names",1,2); + Vector nonameremapping = getOption("no-name-remapping",1,1); + Vector tlsremote = getOption("tls-remote",1,1); + if(tlsremote!=null){ + np.mRemoteCN = tlsremote.get(1); + np.mCheckRemoteCN=true; + np.mX509AuthType = VpnProfile.X509_VERIFY_TLSREMOTE; + + if((compatnames!=null && compatnames.size() > 2) || + (nonameremapping!=null)) + np.mX509AuthType = VpnProfile.X509_VERIFY_TLSREMOTE_COMPAT_NOREMAPPING; + } + + Vector verifyx509name = getOption("verify-x509-name",1,2); + if(verifyx509name!=null){ + np.mRemoteCN = verifyx509name.get(1); + np.mCheckRemoteCN=true; + if(verifyx509name.size()>2) { + if (verifyx509name.get(2).equals("name")) + np.mX509AuthType=VpnProfile.X509_VERIFY_TLSREMOTE_RDN; + else if (verifyx509name.get(2).equals("name-prefix")) + np.mX509AuthType=VpnProfile.X509_VERIFY_TLSREMOTE_RDN_PREFIX; + else + throw new ConfigParseError("Unknown parameter to x509-verify-name: " + verifyx509name.get(2) ); + } else { + np.mX509AuthType = VpnProfile.X509_VERIFY_TLSREMOTE_DN; + } + + } + + + Vector verb = getOption("verb",1,1); + if(verb!=null){ + np.mVerb=verb.get(1); + } + + + if(getOption("nobind", 0, 0) != null) + np.mNobind=true; + + if(getOption("persist-tun", 0,0) != null) + np.mPersistTun=true; + + Vector connectretry = getOption("connect-retry", 1, 1); + if(connectretry!=null) + np.mConnectRetry =connectretry.get(1); + + Vector connectretrymax = getOption("connect-retry-max", 1, 1); + if(connectretrymax!=null) + np.mConnectRetryMax =connectretrymax.get(1); + + Vector> remotetls = getAllOption("remote-cert-tls", 1, 1); + if(remotetls!=null) + if(remotetls.get(0).get(1).equals("server")) + np.mExpectTLSCert=true; + else + options.put("remotetls",remotetls); + + Vector authuser = getOption("auth-user-pass",0,1); + if(authuser !=null){ + if(noauthtypeset) { + np.mAuthenticationType=VpnProfile.TYPE_USERPASS; + } else if(np.mAuthenticationType==VpnProfile.TYPE_CERTIFICATES) { + np.mAuthenticationType=VpnProfile.TYPE_USERPASS_CERTIFICATES; + } else if(np.mAuthenticationType==VpnProfile.TYPE_KEYSTORE) { + np.mAuthenticationType=VpnProfile.TYPE_USERPASS_KEYSTORE; + } + if(authuser.size()>1) { + // Set option value to password get to get cance to embed later. + np.mUsername=null; + np.mPassword=authuser.get(1); + useEmbbedUserAuth(np,authuser.get(1)); + } + } + + // Parse OpenVPN Access Server extra + Vector friendlyname = meta.get("FRIENDLY_NAME"); + if(friendlyname !=null && friendlyname.size() > 1) + np.mName=friendlyname.get(1); + + + Vector ocusername = meta.get("USERNAME"); + if(ocusername !=null && ocusername.size() > 1) + np.mUsername=ocusername.get(1); + + // Check the other options + if(remotes !=null && remotes.size()>1 && extraRemotesAsCustom) { + // first is already added + remotes.remove(0); + np.mCustomConfigOptions += getOptionStrings(remotes); + np.mUseCustomConfig=true; + + } + checkIgnoreAndInvalidOptions(np); + fixup(np); + + return np; + } + + private void checkRedirectParameters(VpnProfile np, Vector> defgw) { + for (Vector redirect: defgw) + for (int i=1;i= 2) { + np.mUsername=parts[0]; + np.mPassword=parts[1]; + } + } + + private void checkIgnoreAndInvalidOptions(VpnProfile np) throws ConfigParseError { + for(String option:unsupportedOptions) + if(options.containsKey(option)) + throw new ConfigParseError(String.format("Unsupported Option %s encountered in config file. Aborting",option)); + + for(String option:ignoreOptions) + // removing an item which is not in the map is no error + options.remove(option); + + + + + if(options.size()> 0) { + np.mCustomConfigOptions += "# These Options were found in the config file do not map to config settings:\n"; + + for(Vector> option:options.values()) { + + np.mCustomConfigOptions += getOptionStrings(option); + + } + np.mUseCustomConfig=true; + + } + } + + + boolean ignoreThisOption(Vector option) { + for (String[] ignoreOption : ignoreOptionsWithArg) { + + if (option.size() < ignoreOption.length) + continue; + + boolean ignore = true; + for (int i = 0; i < ignoreOption.length; i++) { + if (!ignoreOption[i].equals(option.get(i))) + ignore = false; + } + if (ignore) + return true; + + } + return false; + } + + private String getOptionStrings(Vector> option) { + String custom = ""; + for (Vector optionsline : option) { + if (!ignoreThisOption(optionsline)) { + for (String arg : optionsline) + custom += VpnProfile.openVpnEscape(arg) + " "; + custom += "\n"; + } + } + return custom; + } + + + private void fixup(VpnProfile np) { + if(np.mRemoteCN.equals(np.mServerName)) { + np.mRemoteCN=""; + } + } + + private Vector getOption(String option, int minarg, int maxarg) throws ConfigParseError { + Vector> alloptions = getAllOption(option, minarg, maxarg); + if(alloptions==null) + return null; + else + return alloptions.lastElement(); + } + + + private Vector> getAllOption(String option, int minarg, int maxarg) throws ConfigParseError { + Vector> args = options.get(option); + if(args==null) + return null; + + for(Vector optionline:args) + + if(optionline.size()< (minarg+1) || optionline.size() > maxarg+1) { + String err = String.format(Locale.getDefault(),"Option %s has %d parameters, expected between %d and %d", + option,optionline.size()-1,minarg,maxarg ); + throw new ConfigParseError(err); + } + options.remove(option); + return args; + } + +} + + + + diff --git a/app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java b/app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java new file mode 100644 index 00000000..41e8ff0a --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java @@ -0,0 +1,243 @@ +package de.blinkt.openvpn.core; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.NetworkInfo.State; +import android.preference.PreferenceManager; +import de.blinkt.openvpn.R; +import de.blinkt.openvpn.core.VpnStatus.ByteCountListener; + +import java.util.LinkedList; + +import static de.blinkt.openvpn.core.OpenVPNManagement.pauseReason; + +public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountListener { + private int lastNetwork = -1; + private OpenVPNManagement mManagement; + + // Window time in s + private final int TRAFFIC_WINDOW = 60; + // Data traffic limit in bytes + private final long TRAFFIC_LIMIT = 64 * 1024; + + + connectState network = connectState.DISCONNECTED; + connectState screen = connectState.SHOULDBECONNECTED; + connectState userpause = connectState.SHOULDBECONNECTED; + + private String lastStateMsg = null; + + enum connectState { + SHOULDBECONNECTED, + PENDINGDISCONNECT, + DISCONNECTED + } + + static class Datapoint { + private Datapoint(long t, long d) { + timestamp = t; + data = d; + } + + long timestamp; + long data; + } + + LinkedList trafficdata = new LinkedList(); + + @Override + public void updateByteCount(long in, long out, long diffIn, long diffOut) { + if (screen != connectState.PENDINGDISCONNECT) + return; + + long total = diffIn + diffOut; + trafficdata.add(new Datapoint(System.currentTimeMillis(), total)); + + while (trafficdata.getFirst().timestamp <= (System.currentTimeMillis() - TRAFFIC_WINDOW * 1000)) { + trafficdata.removeFirst(); + } + + long windowtraffic = 0; + for (Datapoint dp : trafficdata) + windowtraffic += dp.data; + + if (windowtraffic < TRAFFIC_LIMIT) { + screen = connectState.DISCONNECTED; + VpnStatus.logInfo(R.string.screenoff_pause, + OpenVpnService.humanReadableByteCount(TRAFFIC_LIMIT, false), TRAFFIC_WINDOW); + + mManagement.pause(getPauseReason()); + } + } + + + public void userPause(boolean pause) { + if (pause) { + userpause = connectState.DISCONNECTED; + // Check if we should disconnect + mManagement.pause(getPauseReason()); + } else { + boolean wereConnected = shouldBeConnected(); + userpause = connectState.SHOULDBECONNECTED; + if (shouldBeConnected() && !wereConnected) + mManagement.resume(); + else + // Update the reason why we currently paused + mManagement.pause(getPauseReason()); + } + } + + public DeviceStateReceiver(OpenVPNManagement magnagement) { + super(); + mManagement = magnagement; + } + + + @Override + public void onReceive(Context context, Intent intent) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + + + if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) { + networkStateChange(context); + } else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { + boolean screenOffPause = prefs.getBoolean("screenoff", false); + + if (screenOffPause) { + if (ProfileManager.getLastConnectedVpn()!=null && !ProfileManager.getLastConnectedVpn().mPersistTun) + VpnStatus.logError(R.string.screen_nopersistenttun); + + screen = connectState.PENDINGDISCONNECT; + fillTrafficData(); + if (network == connectState.DISCONNECTED || userpause == connectState.DISCONNECTED) + screen = connectState.DISCONNECTED; + } + } else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) { + // Network was disabled because screen off + boolean connected = shouldBeConnected(); + screen = connectState.SHOULDBECONNECTED; + + /* should be connected has changed because the screen is on now, connect the VPN */ + if (shouldBeConnected() != connected) + mManagement.resume(); + else if (!shouldBeConnected()) + /*Update the reason why we are still paused */ + mManagement.pause(getPauseReason()); + + } + } + + + private void fillTrafficData() { + trafficdata.add(new Datapoint(System.currentTimeMillis(), TRAFFIC_LIMIT)); + } + + + public void networkStateChange(Context context) { + NetworkInfo networkInfo = getCurrentNetworkInfo(context); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + boolean sendusr1 = prefs.getBoolean("netchangereconnect", true); + + + String netstatestring; + if (networkInfo == null) { + netstatestring = "not connected"; + } else { + String subtype = networkInfo.getSubtypeName(); + if (subtype == null) + subtype = ""; + String extrainfo = networkInfo.getExtraInfo(); + if (extrainfo == null) + extrainfo = ""; + + /* + if(networkInfo.getType()==android.net.ConnectivityManager.TYPE_WIFI) { + WifiManager wifiMgr = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + WifiInfo wifiinfo = wifiMgr.getConnectionInfo(); + extrainfo+=wifiinfo.getBSSID(); + + subtype += wifiinfo.getNetworkId(); + }*/ + + + netstatestring = String.format("%2$s %4$s to %1$s %3$s", networkInfo.getTypeName(), + networkInfo.getDetailedState(), extrainfo, subtype); + } + + if (networkInfo != null && networkInfo.getState() == State.CONNECTED) { + int newnet = networkInfo.getType(); + network = connectState.SHOULDBECONNECTED; + + if (lastNetwork != newnet) { + if (screen == connectState.PENDINGDISCONNECT) + screen = connectState.DISCONNECTED; + + if (shouldBeConnected()) { + if (sendusr1) { + if (lastNetwork == -1) { + mManagement.resume(); + } else { + mManagement.reconnect(); + } + } else { + mManagement.networkChange(); + } + } + + + lastNetwork = newnet; + } + } else if (networkInfo == null) { + // Not connected, stop openvpn, set last connected network to no network + lastNetwork = -1; + if (sendusr1) { + network = connectState.DISCONNECTED; + + // Set screen state to be disconnected if disconnect pending + if (screen == connectState.PENDINGDISCONNECT) + screen = connectState.DISCONNECTED; + + mManagement.pause(getPauseReason()); + } + } + + + if (!netstatestring.equals(lastStateMsg)) + VpnStatus.logInfo(R.string.netstatus, netstatestring); + lastStateMsg = netstatestring; + + } + + public boolean isUserPaused() { + return userpause == connectState.DISCONNECTED; + } + + private boolean shouldBeConnected() { + return (screen == connectState.SHOULDBECONNECTED && userpause == connectState.SHOULDBECONNECTED && + network == connectState.SHOULDBECONNECTED); + } + + private pauseReason getPauseReason() { + if (userpause == connectState.DISCONNECTED) + return pauseReason.userPause; + + if (screen == connectState.DISCONNECTED) + return pauseReason.screenOff; + + if (network == connectState.DISCONNECTED) + return pauseReason.noNetwork; + + return pauseReason.userPause; + } + + private NetworkInfo getCurrentNetworkInfo(Context context) { + ConnectivityManager conn = (ConnectivityManager) + context.getSystemService(Context.CONNECTIVITY_SERVICE); + + return conn.getActiveNetworkInfo(); + } +} diff --git a/app/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java b/app/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java new file mode 100644 index 00000000..1daa3433 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java @@ -0,0 +1,14 @@ +package de.blinkt.openvpn.core; + +import android.app.Application; + +/** + * Created by arne on 28.12.13. + */ +public class ICSOpenVPNApplication extends Application { + @Override + public void onCreate() { + super.onCreate(); + PRNGFixes.apply(); + } +} diff --git a/app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java b/app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java new file mode 100644 index 00000000..a2c4796d --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java @@ -0,0 +1,13 @@ +package de.blinkt.openvpn.core; + +import java.security.InvalidKeyException; + +public class NativeUtils { + public static native byte[] rsasign(byte[] input,int pkey) throws InvalidKeyException; + static native void jniclose(int fdint); + + static { + System.loadLibrary("stlport_shared"); + System.loadLibrary("opvpnutil"); + } +} diff --git a/app/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java b/app/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java new file mode 100644 index 00000000..afd01184 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java @@ -0,0 +1,300 @@ +package de.blinkt.openvpn.core; + +import android.os.Build; +import android.text.TextUtils; + +import junit.framework.Assert; + +import java.math.BigInteger; +import java.net.Inet6Address; +import java.util.*; + +import de.blinkt.openvpn.BuildConfig; + +public class NetworkSpace { + + + static class ipAddress implements Comparable { + private BigInteger netAddress; + public int networkMask; + private boolean included; + private boolean isV4; + private BigInteger firstAddress; + private BigInteger lastAddress; + + + @Override + public int compareTo(ipAddress another) { + int comp = getFirstAddress().compareTo(another.getFirstAddress()); + if (comp != 0) + return comp; + + // bigger mask means smaller address block + if (networkMask > another.networkMask) + return -1; + else if (another.networkMask == networkMask) + return 0; + else + return 1; + } + + public ipAddress(CIDRIP ip, boolean include) { + included = include; + netAddress = BigInteger.valueOf(ip.getInt()); + networkMask = ip.len; + isV4 = true; + } + + public ipAddress(Inet6Address address, int mask, boolean include) { + networkMask = mask; + included = include; + + int s = 128; + + netAddress = BigInteger.ZERO; + for (byte b : address.getAddress()) { + s -= 16; + netAddress = netAddress.add(BigInteger.valueOf(b).shiftLeft(s)); + } + } + + public BigInteger getLastAddress() { + if(lastAddress ==null) + lastAddress = getMaskedAddress(true); + return lastAddress; + } + + + public BigInteger getFirstAddress() { + if (firstAddress ==null) + firstAddress =getMaskedAddress(false); + return firstAddress; + } + + + private BigInteger getMaskedAddress(boolean one) { + BigInteger numAddress = netAddress; + + int numBits; + if (isV4) { + numBits = 32 - networkMask; + } else { + numBits = 128 - networkMask; + } + + for (int i = 0; i < numBits; i++) { + if (one) + numAddress = numAddress.setBit(i); + else + numAddress = numAddress.clearBit(i); + } + return numAddress; + } + + + @Override + public String toString() { + //String in = included ? "+" : "-"; + if (isV4) + return String.format(Locale.US,"%s/%d", getIPv4Address(), networkMask); + else + return String.format(Locale.US, "%s/%d", getIPv6Address(), networkMask); + } + + ipAddress(BigInteger baseAddress, int mask, boolean included, boolean isV4) { + this.netAddress = baseAddress; + this.networkMask = mask; + this.included = included; + this.isV4 = isV4; + } + + + public ipAddress[] split() { + ipAddress firsthalf = new ipAddress(getFirstAddress(), networkMask + 1, included, isV4); + ipAddress secondhalf = new ipAddress(firsthalf.getLastAddress().add(BigInteger.ONE), networkMask + 1, included, isV4); + if (BuildConfig.DEBUG) Assert.assertTrue(secondhalf.getLastAddress().equals(getLastAddress())); + return new ipAddress[]{firsthalf, secondhalf}; + } + + String getIPv4Address() { + if (BuildConfig.DEBUG) { + Assert.assertTrue (isV4); + Assert.assertTrue (netAddress.longValue() <= 0xffffffffl); + Assert.assertTrue (netAddress.longValue() >= 0); + } + long ip = netAddress.longValue(); + return String.format(Locale.US, "%d.%d.%d.%d", (ip >> 24) % 256, (ip >> 16) % 256, (ip >> 8) % 256, ip % 256); + } + + String getIPv6Address() { + if (BuildConfig.DEBUG) Assert.assertTrue (!isV4); + BigInteger r = netAddress; + if (r.longValue() == 0) + return "::"; + + Vector parts = new Vector(); + while (r.compareTo(BigInteger.ZERO) == 1) { + parts.add(0, String.format(Locale.US, "%x", r.mod(BigInteger.valueOf(256)).longValue())); + r = r.shiftRight(16); + } + + return TextUtils.join(":", parts); + } + + public boolean containsNet(ipAddress network) { + return getFirstAddress().compareTo(network.getFirstAddress()) != 1 && + getLastAddress().compareTo(network.getLastAddress()) != -1; + } + } + + + TreeSet mIpAddresses = new TreeSet(); + + + public Collection getNetworks(boolean included) { + Vector ips = new Vector(); + for (ipAddress ip : mIpAddresses) { + if (ip.included == included) + ips.add(ip); + } + return ips; + } + + public void clear() { + mIpAddresses.clear(); + } + + + void addIP(CIDRIP cidrIp, boolean include) { + + mIpAddresses.add(new ipAddress(cidrIp, include)); + } + + void addIPv6(Inet6Address address, int mask, boolean included) { + mIpAddresses.add(new ipAddress(address, mask, included)); + } + + TreeSet generateIPList() { + + PriorityQueue networks = new PriorityQueue(mIpAddresses); + + TreeSet ipsDone = new TreeSet(); + + ipAddress currentNet = networks.poll(); + if (currentNet==null) + return ipsDone; + + while (currentNet!=null) { + // Check if it and the next of it are compatbile + ipAddress nextNet = networks.poll(); + + if (BuildConfig.DEBUG) Assert.assertNotNull(currentNet); + if (nextNet== null || currentNet.getLastAddress().compareTo(nextNet.getFirstAddress()) == -1) { + // Everything good, no overlapping nothing to do + ipsDone.add(currentNet); + + currentNet = nextNet; + } else { + // This network is smaller or equal to the next but has the same base address + if (currentNet.getFirstAddress().equals(nextNet.getFirstAddress()) && currentNet.networkMask >= nextNet.networkMask) { + if (currentNet.included == nextNet.included) { + // Included in the next next and same type + // Simply forget our current network + currentNet=nextNet; + } else { + // our currentnet is included in next and types differ. Need to split the next network + ipAddress[] newNets = nextNet.split(); + + // First add the second half to keep the order in networks + if (!networks.contains(newNets[1])) + networks.add(newNets[1]); + + if (newNets[0].getLastAddress().equals(currentNet.getLastAddress())) { + if (BuildConfig.DEBUG) Assert.assertEquals (newNets[0].networkMask, currentNet.networkMask); + // Don't add the lower half that would conflict with currentNet + } else { + if (!networks.contains(newNets[0])) + networks.add(newNets[0]); + } + // Keep currentNet as is + } + } else { + if (BuildConfig.DEBUG) { + Assert.assertTrue(currentNet.networkMask < nextNet.networkMask); + Assert.assertTrue (nextNet.getFirstAddress().compareTo(currentNet.getFirstAddress()) == 1); + Assert.assertTrue (currentNet.getLastAddress().compareTo(nextNet.getLastAddress()) != -1); + } + // This network is bigger than the next and last ip of current >= next + + if (currentNet.included == nextNet.included) { + // Next network is in included in our network with the same type, + // simply ignore the next and move on + } else { + // We need to split our network + ipAddress[] newNets = currentNet.split(); + + + if (newNets[1].networkMask == nextNet.networkMask) { + if (BuildConfig.DEBUG) { + Assert.assertTrue (newNets[1].getFirstAddress().equals(nextNet.getFirstAddress())); + Assert.assertTrue (newNets[1].getLastAddress().equals(currentNet.getLastAddress())); + // Splitted second equal the next network, do not add it + } + networks.add(nextNet); + } else { + // Add the smaller network first + networks.add(newNets[1]); + networks.add(nextNet); + } + currentNet = newNets[0]; + + } + } + } + + } + + return ipsDone; + } + + Collection getPositiveIPList() { + TreeSet ipsSorted = generateIPList(); + + Vector ips = new Vector(); + for (ipAddress ia : ipsSorted) { + if (ia.included) + ips.add(ia); + } + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + // Include postive routes from the original set under < 4.4 since these might overrule the local + // network but only if no smaller negative route exists + for(ipAddress origIp: mIpAddresses){ + if (!origIp.included) + continue; + + // The netspace exists + if(ipsSorted.contains(origIp)) + continue; + + boolean skipIp=false; + // If there is any smaller net that is excluded we may not add the positive route back + for (ipAddress calculatedIp: ipsSorted) { + if(!calculatedIp.included && origIp.containsNet(calculatedIp)) { + skipIp=true; + break; + } + } + if (skipIp) + continue; + + // It is safe to include the IP + ips.add(origIp); + } + + } + + return ips; + } + +} diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java new file mode 100644 index 00000000..a5a3e9f4 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java @@ -0,0 +1,24 @@ +package de.blinkt.openvpn.core; + +public interface OpenVPNManagement { + enum pauseReason { + noNetwork, + userPause, + screenOff + } + + int mBytecountInterval =2; + + void reconnect(); + + void pause(pauseReason reason); + + void resume(); + + boolean stopVPN(); + + /* + * Rebind the interface + */ + void networkChange(); +} diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java new file mode 100644 index 00000000..dacd41c9 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java @@ -0,0 +1,174 @@ +package de.blinkt.openvpn.core; + +import android.util.Log; +import de.blinkt.openvpn.R; +import de.blinkt.openvpn.VpnProfile; +import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus; +import de.blinkt.openvpn.core.VpnStatus.LogItem; + +import java.io.*; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class OpenVPNThread implements Runnable { + private static final String DUMP_PATH_STRING = "Dump path: "; + private static final String TAG = "OpenVPN"; + public static final int M_FATAL = (1 << 4); + public static final int M_NONFATAL = (1 << 5); + public static final int M_WARN = (1 << 6); + public static final int M_DEBUG = (1 << 7); + private String[] mArgv; + private Process mProcess; + private String mNativeDir; + private OpenVpnService mService; + private String mDumpPath; + private Map mProcessEnv; + + public OpenVPNThread(OpenVpnService service,String[] argv, Map processEnv, String nativelibdir) + { + mArgv = argv; + mNativeDir = nativelibdir; + mService = service; + mProcessEnv = processEnv; + } + + public void stopProcess() { + mProcess.destroy(); + } + + + + @Override + public void run() { + try { + Log.i(TAG, "Starting openvpn"); + startOpenVPNThreadArgs(mArgv, mProcessEnv); + Log.i(TAG, "Giving up"); + } catch (Exception e) { + VpnStatus.logException("Starting OpenVPN Thread" ,e); + Log.e(TAG, "OpenVPNThread Got " + e.toString()); + } finally { + int exitvalue = 0; + try { + if (mProcess!=null) + exitvalue = mProcess.waitFor(); + } catch ( IllegalThreadStateException ite) { + VpnStatus.logError("Illegal Thread state: " + ite.getLocalizedMessage()); + } catch (InterruptedException ie) { + VpnStatus.logError("InterruptedException: " + ie.getLocalizedMessage()); + } + if( exitvalue != 0) + VpnStatus.logError("Process exited with exit value " + exitvalue); + + VpnStatus.updateStateString("NOPROCESS", "No process running.", R.string.state_noprocess, ConnectionStatus.LEVEL_NOTCONNECTED); + if(mDumpPath!=null) { + try { + BufferedWriter logout = new BufferedWriter(new FileWriter(mDumpPath + ".log")); + SimpleDateFormat timeformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss",Locale.GERMAN); + for(LogItem li : VpnStatus.getlogbuffer()){ + String time = timeformat.format(new Date(li.getLogtime())); + logout.write(time +" " + li.getString(mService) + "\n"); + } + logout.close(); + VpnStatus.logError(R.string.minidump_generated); + } catch (IOException e) { + VpnStatus.logError("Writing minidump log: " + e.getLocalizedMessage()); + } + } + + mService.processDied(); + Log.i(TAG, "Exiting"); + } + } + + private void startOpenVPNThreadArgs(String[] argv, Map env) { + LinkedList argvlist = new LinkedList(); + + Collections.addAll(argvlist, argv); + + ProcessBuilder pb = new ProcessBuilder(argvlist); + // Hack O rama + + String lbpath = genLibraryPath(argv, pb); + + pb.environment().put("LD_LIBRARY_PATH", lbpath); + + // Add extra variables + for(Entry e:env.entrySet()){ + pb.environment().put(e.getKey(), e.getValue()); + } + pb.redirectErrorStream(true); + try { + mProcess = pb.start(); + // Close the output, since we don't need it + mProcess.getOutputStream().close(); + InputStream in = mProcess.getInputStream(); + BufferedReader br = new BufferedReader(new InputStreamReader(in)); + + while(true) { + String logline = br.readLine(); + if(logline==null) + return; + + if (logline.startsWith(DUMP_PATH_STRING)) + mDumpPath = logline.substring(DUMP_PATH_STRING.length()); + + + // 1380308330.240114 18000002 Send to HTTP proxy: 'X-Online-Host: bla.blabla.com' + + Pattern p = Pattern.compile("(\\d+).(\\d+) ([0-9a-f])+ (.*)"); + Matcher m = p.matcher(logline); + if(m.matches()) { + int flags = Integer.parseInt(m.group(3),16); + String msg = m.group(4); + int logLevel = flags & 0x0F; + + VpnStatus.LogLevel logStatus = VpnStatus.LogLevel.INFO; + + if ((flags & M_FATAL) != 0) + logStatus = VpnStatus.LogLevel.ERROR; + else if ((flags & M_NONFATAL)!=0) + logStatus = VpnStatus.LogLevel.WARNING; + else if ((flags & M_WARN)!=0) + logStatus = VpnStatus.LogLevel.WARNING; + else if ((flags & M_DEBUG)!=0) + logStatus = VpnStatus.LogLevel.VERBOSE; + + if (msg.startsWith("MANAGEMENT: CMD")) + logLevel = Math.max(4, logLevel); + + + VpnStatus.logMessageOpenVPN(logStatus,logLevel,msg); + } else { + VpnStatus.logInfo("P:" + logline); + } + } + + + } catch (IOException e) { + VpnStatus.logException("Error reading from output of OpenVPN process" , e); + stopProcess(); + } + + + } + + private String genLibraryPath(String[] argv, ProcessBuilder pb) { + // Hack until I find a good way to get the real library path + String applibpath = argv[0].replace("/cache/" + VpnProfile.MINIVPN , "/lib"); + + String lbpath = pb.environment().get("LD_LIBRARY_PATH"); + if(lbpath==null) + lbpath = applibpath; + else + lbpath = lbpath + ":" + applibpath; + + if (!applibpath.equals(mNativeDir)) { + lbpath = lbpath + ":" + mNativeDir; + } + return lbpath; + } +} diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java new file mode 100644 index 00000000..6ef41e1b --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java @@ -0,0 +1,598 @@ +package de.blinkt.openvpn.core; + +import android.content.Context; +import android.content.SharedPreferences; +import android.net.LocalServerSocket; +import android.net.LocalSocket; +import android.net.LocalSocketAddress; +import android.os.ParcelFileDescriptor; +import android.preference.PreferenceManager; +import android.util.Log; + +import org.jetbrains.annotations.NotNull; + +import de.blinkt.openvpn.R; +import de.blinkt.openvpn.VpnProfile; +import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.*; + +public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { + + private static final String TAG = "openvpn"; + private LocalSocket mSocket; + private VpnProfile mProfile; + private OpenVpnService mOpenVPNService; + private LinkedList mFDList=new LinkedList(); + private LocalServerSocket mServerSocket; + private boolean mReleaseHold=true; + private boolean mWaitingForRelease=false; + private long mLastHoldRelease=0; + + private static Vector active=new Vector(); + private LocalSocket mServerSocketLocal; + + private pauseReason lastPauseReason = pauseReason.noNetwork; + + public OpenVpnManagementThread(VpnProfile profile, OpenVpnService openVpnService) { + mProfile = profile; + mOpenVPNService = openVpnService; + + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(openVpnService); + boolean managemeNetworkState = prefs.getBoolean("netchangereconnect", true); + if(managemeNetworkState) + mReleaseHold=false; + + } + + public boolean openManagementInterface(@NotNull Context c) { + // Could take a while to open connection + int tries=8; + + String socketName = (c.getCacheDir().getAbsolutePath() + "/" + "mgmtsocket"); + // The mServerSocketLocal is transferred to the LocalServerSocket, ignore warning + + mServerSocketLocal = new LocalSocket(); + + while(tries > 0 && !mServerSocketLocal.isConnected()) { + try { + mServerSocketLocal.bind(new LocalSocketAddress(socketName, + LocalSocketAddress.Namespace.FILESYSTEM)); + } catch (IOException e) { + // wait 300 ms before retrying + try { Thread.sleep(300); + } catch (InterruptedException e1) { + } + + } + tries--; + } + + try { + + mServerSocket = new LocalServerSocket(mServerSocketLocal.getFileDescriptor()); + return true; + } catch (IOException e) { + VpnStatus.logException(e); + } + return false; + + + } + + public void managmentCommand(String cmd) { + try { + if(mSocket!=null && mSocket.getOutputStream() !=null) { + mSocket.getOutputStream().write(cmd.getBytes()); + mSocket.getOutputStream().flush(); + } + }catch (IOException e) { + // Ignore socket stack traces + } + } + + + @Override + public void run() { + byte [] buffer =new byte[2048]; + // mSocket.setSoTimeout(5); // Setting a timeout cannot be that bad + + String pendingInput=""; + active.add(this); + + try { + // Wait for a client to connect + mSocket= mServerSocket.accept(); + InputStream instream = mSocket.getInputStream(); + // Close the management socket after client connected + + mServerSocket.close(); + // Closing one of the two sockets also closes the other + //mServerSocketLocal.close(); + + while(true) { + int numbytesread = instream.read(buffer); + if(numbytesread==-1) + return; + + FileDescriptor[] fds = null; + try { + fds = mSocket.getAncillaryFileDescriptors(); + } catch (IOException e) { + VpnStatus.logException("Error reading fds from socket", e); + } + if(fds!=null){ + Collections.addAll(mFDList, fds); + } + + String input = new String(buffer,0,numbytesread,"UTF-8"); + + pendingInput += input; + + pendingInput=processInput(pendingInput); + + + + } + } catch (IOException e) { + if (!e.getMessage().equals("socket closed")) + VpnStatus.logException(e); + } + active.remove(this); + } + + //! Hack O Rama 2000! + private void protectFileDescriptor(FileDescriptor fd) { + Exception exp; + try { + Method getInt = FileDescriptor.class.getDeclaredMethod("getInt$"); + int fdint = (Integer) getInt.invoke(fd); + + // You can even get more evil by parsing toString() and extract the int from that :) + + boolean result = mOpenVPNService.protect(fdint); + if (!result) + VpnStatus.logWarning("Could not protect VPN socket"); + + + //ParcelFileDescriptor pfd = ParcelFileDescriptor.fromFd(fdint); + //pfd.close(); + NativeUtils.jniclose(fdint); + return; + } catch (NoSuchMethodException e) { + exp =e; + } catch (IllegalArgumentException e) { + exp =e; + } catch (IllegalAccessException e) { + exp =e; + } catch (InvocationTargetException e) { + exp =e; + } catch (NullPointerException e) { + exp =e; + } + + Log.d("Openvpn", "Failed to retrieve fd from socket: " + fd); + VpnStatus.logException("Failed to retrieve fd from socket (" + fd + ")" , exp); + } + + private String processInput(String pendingInput) { + + + while(pendingInput.contains("\n")) { + String[] tokens = pendingInput.split("\\r?\\n", 2); + processCommand(tokens[0]); + if(tokens.length == 1) + // No second part, newline was at the end + pendingInput=""; + else + pendingInput=tokens[1]; + } + return pendingInput; + } + + + private void processCommand(String command) { + //Log.i(TAG, "Line from managment" + command); + + + if (command.startsWith(">") && command.contains(":")) { + String[] parts = command.split(":",2); + String cmd = parts[0].substring(1); + String argument = parts[1]; + + + if(cmd.equals("INFO")) { + /* Ignore greeting from management */ + return; + }else if (cmd.equals("PASSWORD")) { + processPWCommand(argument); + } else if (cmd.equals("HOLD")) { + handleHold(); + } else if (cmd.equals("NEED-OK")) { + processNeedCommand(argument); + } else if (cmd.equals("BYTECOUNT")){ + processByteCount(argument); + } else if (cmd.equals("STATE")) { + processState(argument); + } else if (cmd.equals("PROXY")) { + processProxyCMD(argument); + } else if (cmd.equals("LOG")) { + processLogMessage(argument); + } else if (cmd.equals("RSA_SIGN")) { + processSignCommand(argument); + } else { + VpnStatus.logWarning("MGMT: Got unrecognized command" + command); + Log.i(TAG, "Got unrecognized command" + command); + } + } else if (command.startsWith("SUCCESS:")) { + /* Ignore this kind of message too */ + return; + } else if (command.startsWith("PROTECTFD: ")) { + FileDescriptor fdtoprotect = mFDList.pollFirst(); + if (fdtoprotect!=null) + protectFileDescriptor(fdtoprotect); + } else { + Log.i(TAG, "Got unrecognized line from managment" + command); + VpnStatus.logWarning("MGMT: Got unrecognized line from management:" + command); + } + } + + private void processLogMessage(String argument) { + String[] args = argument.split(",",4); + // 0 unix time stamp + // 1 log level N,I,E etc. + /* + (b) zero or more message flags in a single string: + I -- informational + F -- fatal error + N -- non-fatal error + W -- warning + D -- debug, and + */ + // 2 log message + + Log.d("OpenVPN", argument); + + VpnStatus.LogLevel level; + if (args[1].equals("I")) { + level = VpnStatus.LogLevel.INFO; + } else if (args[1].equals("W")) { + level = VpnStatus.LogLevel.WARNING; + } else if (args[1].equals("D")) { + level = VpnStatus.LogLevel.VERBOSE; + } else if (args[1].equals("F")) { + level = VpnStatus.LogLevel.ERROR; + } else { + level = VpnStatus.LogLevel.INFO; + } + + int ovpnlevel = Integer.parseInt(args[2]) & 0x0F; + String msg = args[3]; + + if (msg.startsWith("MANAGEMENT: CMD")) + ovpnlevel = Math.max(4, ovpnlevel); + + VpnStatus.logMessageOpenVPN(level,ovpnlevel, msg); + } + + private void handleHold() { + if(mReleaseHold) { + releaseHoldCmd(); + } else { + mWaitingForRelease=true; + + VpnStatus.updateStatePause(lastPauseReason); + + + } + } + private void releaseHoldCmd() { + if ((System.currentTimeMillis()- mLastHoldRelease) < 5000) { + try { + Thread.sleep(3000); + } catch (InterruptedException e) { + } + + } + mWaitingForRelease=false; + mLastHoldRelease = System.currentTimeMillis(); + managmentCommand("hold release\n"); + managmentCommand("bytecount " + mBytecountInterval + "\n"); + managmentCommand("state on\n"); + //managmentCommand("log on all\n"); + } + + public void releaseHold() { + mReleaseHold=true; + if(mWaitingForRelease) + releaseHoldCmd(); + + } + + private void processProxyCMD(String argument) { + String[] args = argument.split(",",3); + SocketAddress proxyaddr = ProxyDetection.detectProxy(mProfile); + + + if(args.length >= 2) { + String proto = args[1]; + if(proto.equals("UDP")) { + proxyaddr=null; + } + } + + if(proxyaddr instanceof InetSocketAddress ){ + InetSocketAddress isa = (InetSocketAddress) proxyaddr; + + VpnStatus.logInfo(R.string.using_proxy, isa.getHostName(), isa.getPort()); + + String proxycmd = String.format(Locale.ENGLISH,"proxy HTTP %s %d\n", isa.getHostName(),isa.getPort()); + managmentCommand(proxycmd); + } else { + managmentCommand("proxy NONE\n"); + } + + } + private void processState(String argument) { + String[] args = argument.split(",",3); + String currentstate = args[1]; + + if(args[2].equals(",,")) + VpnStatus.updateStateString(currentstate, ""); + else + VpnStatus.updateStateString(currentstate, args[2]); + } + + + private void processByteCount(String argument) { + // >BYTECOUNT:{BYTES_IN},{BYTES_OUT} + int comma = argument.indexOf(','); + long in = Long.parseLong(argument.substring(0, comma)); + long out = Long.parseLong(argument.substring(comma+1)); + + VpnStatus.updateByteCount(in, out); + + } + + + + private void processNeedCommand(String argument) { + int p1 =argument.indexOf('\''); + int p2 = argument.indexOf('\'',p1+1); + + String needed = argument.substring(p1+1, p2); + String extra = argument.split(":",2)[1]; + + String status = "ok"; + + + if (needed.equals("PROTECTFD")) { + FileDescriptor fdtoprotect = mFDList.pollFirst(); + protectFileDescriptor(fdtoprotect); + } else if (needed.equals("DNSSERVER")) { + mOpenVPNService.addDNS(extra); + }else if (needed.equals("DNSDOMAIN")){ + mOpenVPNService.setDomain(extra); + } else if (needed.equals("ROUTE")) { + String[] routeparts = extra.split(" "); + + /* + buf_printf (&out, "%s %s %s dev %s", network, netmask, gateway, rgi->iface); + else + buf_printf (&out, "%s %s %s", network, netmask, gateway); + */ + + if(routeparts.length==5) { + assert(routeparts[3].equals("dev")); + mOpenVPNService.addRoute(routeparts[0], routeparts[1], routeparts[2], routeparts[4]); + } else if (routeparts.length >= 3) { + mOpenVPNService.addRoute(routeparts[0], routeparts[1], routeparts[2], null); + } else { + VpnStatus.logError("Unrecognized ROUTE cmd:" + Arrays.toString(routeparts) + " | " + argument); + } + + } else if (needed.equals("ROUTE6")) { + String[] routeparts = extra.split(" "); + mOpenVPNService.addRoutev6(routeparts[0],routeparts[1]); + } else if (needed.equals("IFCONFIG")) { + String[] ifconfigparts = extra.split(" "); + int mtu = Integer.parseInt(ifconfigparts[2]); + mOpenVPNService.setLocalIP(ifconfigparts[0], ifconfigparts[1],mtu,ifconfigparts[3]); + } else if (needed.equals("IFCONFIG6")) { + mOpenVPNService.setLocalIPv6(extra); + + } else if (needed.equals("PERSIST_TUN_ACTION")) { + // check if tun cfg stayed the same + status = mOpenVPNService.getTunReopenStatus(); + } else if (needed.equals("OPENTUN")) { + if(sendTunFD(needed,extra)) + return; + else + status="cancel"; + // This not nice or anything but setFileDescriptors accepts only FilDescriptor class :( + + } else { + Log.e(TAG,"Unkown needok command " + argument); + return; + } + + String cmd = String.format("needok '%s' %s\n", needed, status); + managmentCommand(cmd); + } + + private boolean sendTunFD (String needed, String extra) { + Exception exp; + if(!extra.equals("tun")) { + // We only support tun + VpnStatus.logError(String.format("Device type %s requested, but only tun is possible with the Android API, sorry!",extra)); + + return false; + } + ParcelFileDescriptor pfd = mOpenVPNService.openTun(); + if(pfd==null) + return false; + + Method setInt; + int fdint = pfd.getFd(); + try { + setInt = FileDescriptor.class.getDeclaredMethod("setInt$",int.class); + FileDescriptor fdtosend = new FileDescriptor(); + + setInt.invoke(fdtosend,fdint); + + FileDescriptor[] fds = {fdtosend}; + mSocket.setFileDescriptorsForSend(fds); + + // Trigger a send so we can close the fd on our side of the channel + // The API documentation fails to mention that it will not reset the file descriptor to + // be send and will happily send the file descriptor on every write ... + String cmd = String.format("needok '%s' %s\n", needed, "ok"); + managmentCommand(cmd); + + // Set the FileDescriptor to null to stop this mad behavior + mSocket.setFileDescriptorsForSend(null); + + pfd.close(); + + return true; + } catch (NoSuchMethodException e) { + exp =e; + } catch (IllegalArgumentException e) { + exp =e; + } catch (IllegalAccessException e) { + exp =e; + } catch (InvocationTargetException e) { + exp =e; + } catch (IOException e) { + exp =e; + } + VpnStatus.logException("Could not send fd over socket" , exp); + + return false; + } + + private void processPWCommand(String argument) { + //argument has the form Need 'Private Key' password + // or ">PASSWORD:Verification Failed: '%s' ['%s']" + String needed; + + + + try{ + + int p1 = argument.indexOf('\''); + int p2 = argument.indexOf('\'',p1+1); + needed = argument.substring(p1+1, p2); + if (argument.startsWith("Verification Failed")) { + proccessPWFailed(needed, argument.substring(p2+1)); + return; + } + } catch (StringIndexOutOfBoundsException sioob) { + VpnStatus.logError("Could not parse management Password command: " + argument); + return; + } + + String pw=null; + + if(needed.equals("Private Key")) { + pw = mProfile.getPasswordPrivateKey(); + } else if (needed.equals("Auth")) { + String usercmd = String.format("username '%s' %s\n", + needed, VpnProfile.openVpnEscape(mProfile.mUsername)); + managmentCommand(usercmd); + pw = mProfile.getPasswordAuth(); + } + if(pw!=null) { + String cmd = String.format("password '%s' %s\n", needed, VpnProfile.openVpnEscape(pw)); + managmentCommand(cmd); + } else { + VpnStatus.logError(String.format("Openvpn requires Authentication type '%s' but no password/key information available", needed)); + } + + } + + + + + private void proccessPWFailed(String needed, String args) { + VpnStatus.updateStateString("AUTH_FAILED", needed + args, R.string.state_auth_failed, ConnectionStatus.LEVEL_AUTH_FAILED); + } + + + private static boolean stopOpenVPN() { + boolean sendCMD=false; + for (OpenVpnManagementThread mt: active){ + mt.managmentCommand("signal SIGINT\n"); + sendCMD=true; + try { + if(mt.mSocket !=null) + mt.mSocket.close(); + } catch (IOException e) { + // Ignore close error on already closed socket + } + } + return sendCMD; + } + + @Override + public void networkChange() { + if(!mWaitingForRelease) + managmentCommand("network-change\n"); + } + + public void signalusr1() { + mReleaseHold=false; + + if(!mWaitingForRelease) + managmentCommand("signal SIGUSR1\n"); + else + // If signalusr1 is called update the state string + // if there is another for stopping + VpnStatus.updateStatePause(lastPauseReason); + } + + public void reconnect() { + signalusr1(); + releaseHold(); + } + + private void processSignCommand(String b64data) { + + String signed_string = mProfile.getSignedData(b64data); + if(signed_string==null) { + managmentCommand("rsa-sig\n"); + managmentCommand("\nEND\n"); + stopOpenVPN(); + return; + } + managmentCommand("rsa-sig\n"); + managmentCommand(signed_string); + managmentCommand("\nEND\n"); + } + + @Override + public void pause (pauseReason reason) { + lastPauseReason = reason; + signalusr1(); + } + + @Override + public void resume() { + releaseHold(); + /* Reset the reason why we are disconnected */ + lastPauseReason = pauseReason.noNetwork; + } + + @Override + public boolean stopVPN() { + return stopOpenVPN(); + } +} diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVpnService.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVpnService.java new file mode 100644 index 00000000..b1f9dbd4 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVpnService.java @@ -0,0 +1,757 @@ +package de.blinkt.openvpn.core; + +import android.Manifest.permission; +import android.annotation.TargetApi; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.net.ConnectivityManager; +import android.net.VpnService; +import android.os.Binder; +import android.os.Build; +import android.os.Handler.Callback; +import android.os.IBinder; +import android.os.Message; +import android.os.ParcelFileDescriptor; +import android.preference.PreferenceManager; +import android.text.TextUtils; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Locale; +import java.util.Vector; + +import de.blinkt.openvpn.BuildConfig; +import de.blinkt.openvpn.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; + +import static de.blinkt.openvpn.core.NetworkSpace.ipAddress; +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; + +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"; + public static final String ALWAYS_SHOW_NOTIFICATION = "de.blinkt.openvpn.NOTIFICATION_ALWAYS_VISIBLE"; + public static final String DISCONNECT_VPN = "de.blinkt.openvpn.DISCONNECT_VPN"; + private static final String PAUSE_VPN = "de.blinkt.openvpn.PAUSE_VPN"; + private static final String RESUME_VPN = "de.blinkt.openvpn.RESUME_VPN"; + private static final int OPENVPN_STATUS = 1; + private static boolean mNotificationAlwaysVisible = false; + private final Vector mDnslist = new Vector(); + private final NetworkSpace mRoutes = new NetworkSpace(); + private final NetworkSpace mRoutesv6 = new NetworkSpace(); + private final IBinder mBinder = new LocalBinder(); + private Thread mProcessThread = null; + private VpnProfile mProfile; + private String mDomain = null; + private CIDRIP mLocalIP = null; + private int mMtu; + private String mLocalIPv6 = null; + private DeviceStateReceiver mDeviceStateReceiver; + private boolean mDisplayBytecount = false; + private boolean mStarting = false; + private long mConnecttime; + private boolean mOvpn3 = false; + private OpenVPNManagement mManagement; + private String mLastTunCfg; + private String mRemoteGW; + + // 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) + bytes = bytes * 8; + int unit = mbit ? 1000 : 1024; + if (bytes < unit) + return bytes + (mbit ? " bit" : " B"); + + int exp = (int) (Math.log(bytes) / Math.log(unit)); + String pre = (mbit ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (mbit ? "" : ""); + if (mbit) + return String.format(Locale.getDefault(), "%.1f %sbit", bytes / Math.pow(unit, exp), pre); + else + return String.format(Locale.getDefault(), "%.1f %sB", bytes / Math.pow(unit, exp), pre); + } + + @Override + public IBinder onBind(Intent intent) { + String action = intent.getAction(); + if (action != null && action.equals(START_SERVICE)) + return mBinder; + else + return super.onBind(intent); + } + + @Override + public void onRevoke() { + mManagement.stopVPN(); + endVpnService(); + } + + // Similar to revoke but do not try to stop process + public void processDied() { + endVpnService(); + } + + private void endVpnService() { + mProcessThread = null; + VpnStatus.removeByteCountListener(this); + unregisterDeviceStateReceiver(); + ProfileManager.setConntectedVpnProfileDisconnected(this); + if (!mStarting) { + stopForeground(!mNotificationAlwaysVisible); + + if (!mNotificationAlwaysVisible) { + stopSelf(); + VpnStatus.removeStateListener(this); + } + } + } + + private void showNotification(String msg, String tickerText, boolean lowpriority, long when, ConnectionStatus status) { + String ns = Context.NOTIFICATION_SERVICE; + NotificationManager mNotificationManager = (NotificationManager) getSystemService(ns); + + + int icon = getIconByConnectionStatus(status); + + android.app.Notification.Builder nbuilder = new Notification.Builder(this); + + if (mProfile != null) + nbuilder.setContentTitle(getString(R.string.notifcation_title, mProfile.mName)); + else + nbuilder.setContentTitle(getString(R.string.notifcation_title_notconnect)); + + nbuilder.setContentText(msg); + nbuilder.setOnlyAlertOnce(true); + nbuilder.setOngoing(true); + nbuilder.setContentIntent(getLogPendingIntent()); + nbuilder.setSmallIcon(icon); + + + if (when != 0) + nbuilder.setWhen(when); + + + // Try to set the priority available since API 16 (Jellybean) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) + jbNotificationExtras(lowpriority, nbuilder); + + if (tickerText != null && !tickerText.equals("")) + nbuilder.setTicker(tickerText); + + @SuppressWarnings("deprecation") + Notification notification = nbuilder.getNotification(); + + + mNotificationManager.notify(OPENVPN_STATUS, notification); + startForeground(OPENVPN_STATUS, notification); + } + + private int getIconByConnectionStatus(ConnectionStatus level) { + switch (level) { + case LEVEL_CONNECTED: + return R.drawable.ic_stat_vpn; + case LEVEL_AUTH_FAILED: + case LEVEL_NONETWORK: + case LEVEL_NOTCONNECTED: + return R.drawable.ic_stat_vpn_offline; + case LEVEL_CONNECTING_NO_SERVER_REPLY_YET: + case LEVEL_WAITING_FOR_USER_INPUT: + return R.drawable.ic_stat_vpn_outline; + case LEVEL_CONNECTING_SERVER_REPLIED: + return R.drawable.ic_stat_vpn_empty_halo; + case LEVEL_VPNPAUSED: + return android.R.drawable.ic_media_pause; + case UNKNOWN_LEVEL: + default: + return R.drawable.ic_stat_vpn; + + } + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + private void jbNotificationExtras(boolean lowpriority, + android.app.Notification.Builder nbuilder) { + try { + if (lowpriority) { + Method setpriority = nbuilder.getClass().getMethod("setPriority", int.class); + // PRIORITY_MIN == -2 + setpriority.invoke(nbuilder, -2); + + Method setUsesChronometer = nbuilder.getClass().getMethod("setUsesChronometer", boolean.class); + setUsesChronometer.invoke(nbuilder, true); + + } + + Intent disconnectVPN = new Intent(this, DisconnectVPN.class); + disconnectVPN.setAction(DISCONNECT_VPN); + PendingIntent disconnectPendingIntent = PendingIntent.getActivity(this, 0, disconnectVPN, 0); + + nbuilder.addAction(android.R.drawable.ic_menu_close_clear_cancel, + getString(R.string.cancel_connection), disconnectPendingIntent); + + Intent pauseVPN = new Intent(this, OpenVpnService.class); + if (mDeviceStateReceiver == null || !mDeviceStateReceiver.isUserPaused()) { + pauseVPN.setAction(PAUSE_VPN); + PendingIntent pauseVPNPending = PendingIntent.getService(this, 0, pauseVPN, 0); + nbuilder.addAction(android.R.drawable.ic_media_pause, + getString(R.string.pauseVPN), pauseVPNPending); + + } else { + pauseVPN.setAction(RESUME_VPN); + PendingIntent resumeVPNPending = PendingIntent.getService(this, 0, pauseVPN, 0); + nbuilder.addAction(android.R.drawable.ic_media_play, + getString(R.string.resumevpn), resumeVPNPending); + } + + + //ignore exception + } catch (NoSuchMethodException nsm) { + VpnStatus.logException(nsm); + } catch (IllegalArgumentException e) { + VpnStatus.logException(e); + } catch (IllegalAccessException e) { + VpnStatus.logException(e); + } catch (InvocationTargetException e) { + VpnStatus.logException(e); + } + + } + + PendingIntent getLogPendingIntent() { + // Let the configure Button show the Log + Intent intent = new Intent(getBaseContext(), LogWindow.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); + return startLW; + + } + + synchronized void registerDeviceStateReceiver(OpenVPNManagement magnagement) { + // Registers BroadcastReceiver to track network connection changes. + IntentFilter filter = new IntentFilter(); + filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + filter.addAction(Intent.ACTION_SCREEN_OFF); + filter.addAction(Intent.ACTION_SCREEN_ON); + mDeviceStateReceiver = new DeviceStateReceiver(magnagement); + registerReceiver(mDeviceStateReceiver, filter); + VpnStatus.addByteCountListener(mDeviceStateReceiver); + } + + synchronized void unregisterDeviceStateReceiver() { + if (mDeviceStateReceiver != null) + try { + VpnStatus.removeByteCountListener(mDeviceStateReceiver); + this.unregisterReceiver(mDeviceStateReceiver); + } catch (IllegalArgumentException iae) { + // I don't know why this happens: + // java.lang.IllegalArgumentException: Receiver not registered: de.blinkt.openvpn.NetworkSateReceiver@41a61a10 + // Ignore for now ... + iae.printStackTrace(); + } + mDeviceStateReceiver = null; + } + + public void userPause(boolean shouldBePaused) { + if (mDeviceStateReceiver != null) + mDeviceStateReceiver.userPause(shouldBePaused); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + + if (intent != null && intent.getBooleanExtra(ALWAYS_SHOW_NOTIFICATION, false)) + mNotificationAlwaysVisible = true; + + VpnStatus.addStateListener(this); + VpnStatus.addByteCountListener(this); + + if (intent != null && PAUSE_VPN.equals(intent.getAction())) { + if (mDeviceStateReceiver != null) + mDeviceStateReceiver.userPause(true); + return START_NOT_STICKY; + } + + if (intent != null && RESUME_VPN.equals(intent.getAction())) { + if (mDeviceStateReceiver != null) + mDeviceStateReceiver.userPause(false); + return START_NOT_STICKY; + } + + + if (intent != null && START_SERVICE.equals(intent.getAction())) + return START_NOT_STICKY; + if (intent != null && START_SERVICE_STICKY.equals(intent.getAction())) { + return START_REDELIVER_INTENT; + } + + if (intent == null) + return START_NOT_STICKY; + + // Extract information from the intent. + String prefix = getPackageName(); + String[] argv = intent.getStringArrayExtra(prefix + ".ARGV"); + String nativelibdir = intent.getStringExtra(prefix + ".nativelib"); + String profileUUID = intent.getStringExtra(prefix + ".profileUUID"); + + mProfile = ProfileManager.get(this, profileUUID); + + String startTitle = getString(R.string.start_vpn_title, mProfile.mName); + String startTicker = getString(R.string.start_vpn_ticker, mProfile.mName); + showNotification(startTitle, startTicker, + false, 0, LEVEL_CONNECTING_NO_SERVER_REPLY_YET); + + // Set a flag that we are starting a new VPN + mStarting = true; + // Stop the previous session by interrupting the thread. + if (mManagement != null && mManagement.stopVPN()) + // an old was asked to exit, wait 1s + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + //ignore + } + + + if (mProcessThread != null) { + mProcessThread.interrupt(); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + //ignore + } + } + // An old running VPN should now be exited + mStarting = false; + + // Start a new session by creating a new thread. + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + + mOvpn3 = prefs.getBoolean("ovpn3", false); + if (!"ovpn3".equals(BuildConfig.FLAVOR)) + mOvpn3 = false; + + + // Open the Management Interface + if (!mOvpn3) { + + // start a Thread that handles incoming messages of the managment socket + OpenVpnManagementThread ovpnManagementThread = new OpenVpnManagementThread(mProfile, this); + if (ovpnManagementThread.openManagementInterface(this)) { + + Thread mSocketManagerThread = new Thread(ovpnManagementThread, "OpenVPNManagementThread"); + mSocketManagerThread.start(); + mManagement = ovpnManagementThread; + VpnStatus.logInfo("started Socket Thread"); + } else { + return START_NOT_STICKY; + } + } + + + Runnable processThread; + if (mOvpn3) { + + OpenVPNManagement mOpenVPN3 = instantiateOpenVPN3Core(); + processThread = (Runnable) mOpenVPN3; + mManagement = mOpenVPN3; + + + } else { + HashMap env = new HashMap(); + processThread = new OpenVPNThread(this, argv, env, nativelibdir); + } + + mProcessThread = new Thread(processThread, "OpenVPNProcessThread"); + mProcessThread.start(); + + if (mDeviceStateReceiver != null) + unregisterDeviceStateReceiver(); + + registerDeviceStateReceiver(mManagement); + + + ProfileManager.setConnectedVpnProfile(this, mProfile); + + return START_NOT_STICKY; + } + + private OpenVPNManagement instantiateOpenVPN3Core() { + try { + Class cl = Class.forName("de.blinkt.openvpn.core.OpenVPNThreadv3"); + return (OpenVPNManagement) cl.getConstructor(OpenVpnService.class,VpnProfile.class).newInstance(this,mProfile); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + return null; + } + + @Override + public void onDestroy() { + if (mProcessThread != null) { + mManagement.stopVPN(); + + mProcessThread.interrupt(); + } + if (mDeviceStateReceiver != null) { + this.unregisterReceiver(mDeviceStateReceiver); + } + // Just in case unregister for state + VpnStatus.removeStateListener(this); + + } + + private String getTunConfigString() { + // The format of the string is not important, only that + // two identical configurations produce the same result + String cfg = "TUNCFG UNQIUE STRING ips:"; + + if (mLocalIP != null) + cfg += mLocalIP.toString(); + if (mLocalIPv6 != null) + cfg += mLocalIPv6; + + cfg += "routes: " + TextUtils.join("|", mRoutes.getNetworks(true)) + TextUtils.join("|", mRoutesv6.getNetworks(true)); + cfg += "excl. routes:" + TextUtils.join("|", mRoutes.getNetworks(false)) + TextUtils.join("|", mRoutesv6.getNetworks(false)); + cfg += "dns: " + TextUtils.join("|", mDnslist); + cfg += "domain: " + mDomain; + cfg += "mtu: " + mMtu; + return cfg; + } + + public ParcelFileDescriptor openTun() { + + //Debug.startMethodTracing(getExternalFilesDir(null).toString() + "/opentun.trace", 40* 1024 * 1024); + + Builder builder = new Builder(); + + VpnStatus.logInfo(R.string.last_openvpn_tun_config); + + + if (mLocalIP == null && mLocalIPv6 == null) { + VpnStatus.logError(getString(R.string.opentun_no_ipaddr)); + return null; + } + + if (mLocalIP != null) { + try { + builder.addAddress(mLocalIP.mIp, mLocalIP.len); + } catch (IllegalArgumentException iae) { + VpnStatus.logError(R.string.dns_add_error, mLocalIP, iae.getLocalizedMessage()); + return null; + } + } + + if (mLocalIPv6 != null) { + String[] ipv6parts = mLocalIPv6.split("/"); + try { + builder.addAddress(ipv6parts[0], Integer.parseInt(ipv6parts[1])); + } catch (IllegalArgumentException iae) { + VpnStatus.logError(R.string.ip_add_error, mLocalIPv6, iae.getLocalizedMessage()); + return null; + } + + } + + + for (String dns : mDnslist) { + try { + builder.addDnsServer(dns); + } catch (IllegalArgumentException iae) { + VpnStatus.logError(R.string.dns_add_error, dns, iae.getLocalizedMessage()); + } + } + + + builder.setMtu(mMtu); + + Collection positiveIPv4Routes = mRoutes.getPositiveIPList(); + Collection positiveIPv6Routes = mRoutesv6.getPositiveIPList(); + + for (NetworkSpace.ipAddress route : positiveIPv4Routes) { + try { + builder.addRoute(route.getIPv4Address(), route.networkMask); + } catch (IllegalArgumentException ia) { + VpnStatus.logError(getString(R.string.route_rejected) + route + " " + ia.getLocalizedMessage()); + } + } + + for (NetworkSpace.ipAddress route6 : positiveIPv6Routes) { + try { + builder.addRoute(route6.getIPv6Address(), route6.networkMask); + } catch (IllegalArgumentException ia) { + VpnStatus.logError(getString(R.string.route_rejected) + route6 + " " + ia.getLocalizedMessage()); + } + } + + if (mDomain != null) + builder.addSearchDomain(mDomain); + + VpnStatus.logInfo(R.string.local_ip_info, mLocalIP.mIp, mLocalIP.len, mLocalIPv6, mMtu); + VpnStatus.logInfo(R.string.dns_server_info, TextUtils.join(", ", mDnslist), mDomain); + VpnStatus.logInfo(R.string.routes_info_incl, TextUtils.join(", ", mRoutes.getNetworks(true)), TextUtils.join(", ", mRoutesv6.getNetworks(true))); + VpnStatus.logInfo(R.string.routes_info_excl, TextUtils.join(", ", mRoutes.getNetworks(false)),TextUtils.join(", ", mRoutesv6.getNetworks(false))); + VpnStatus.logDebug(R.string.routes_debug, TextUtils.join(", ", positiveIPv4Routes), TextUtils.join(", ", positiveIPv6Routes)); + + String session = mProfile.mName; + if (mLocalIP != null && mLocalIPv6 != null) + session = getString(R.string.session_ipv6string, session, mLocalIP, mLocalIPv6); + else if (mLocalIP != null) + session = getString(R.string.session_ipv4string, session, mLocalIP); + + builder.setSession(session); + + // No DNS Server, log a warning + if (mDnslist.size() == 0) + VpnStatus.logInfo(R.string.warn_no_dns); + + mLastTunCfg = getTunConfigString(); + + // Reset information + mDnslist.clear(); + mRoutes.clear(); + mRoutesv6.clear(); + mLocalIP = null; + mLocalIPv6 = null; + mDomain = null; + + builder.setConfigureIntent(getLogPendingIntent()); + + try { + //Debug.stopMethodTracing(); + return builder.establish(); + } catch (Exception e) { + VpnStatus.logError(R.string.tun_open_error); + VpnStatus.logError(getString(R.string.error) + e.getLocalizedMessage()); + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR1) { + VpnStatus.logError(R.string.tun_error_helpful); + } + return null; + } + + } + + public void addDNS(String dns) { + mDnslist.add(dns); + } + + public void setDomain(String domain) { + if (mDomain == null) { + mDomain = domain; + } + } + + /** Route that is always included, used by the v3 core */ + public void addRoute (CIDRIP route) { + mRoutes.addIP(route, true); + } + + public void addRoute (String dest, String mask, String gateway, String device) { + CIDRIP route = new CIDRIP(dest, mask); + boolean include = isAndroidTunDevice(device); + + NetworkSpace.ipAddress gatewayIP = new NetworkSpace.ipAddress(new CIDRIP(gateway, 32),false); + + if (mLocalIP==null) { + VpnStatus.logError("Local IP address unset but adding route?! This is broken! Please contact author with log"); + return; + } + NetworkSpace.ipAddress localNet = new NetworkSpace.ipAddress(mLocalIP,true); + if (localNet.containsNet(gatewayIP)) + include=true; + + if (gateway!= null && + (gateway.equals("255.255.255.255") || gateway.equals(mRemoteGW))) + include=true; + + + if (route.len == 32 && !mask.equals("255.255.255.255")) { + VpnStatus.logWarning(R.string.route_not_cidr, dest, mask); + } + + if (route.normalise()) + VpnStatus.logWarning(R.string.route_not_netip, dest, route.len, route.mIp); + + mRoutes.addIP(route, include); + } + + public void addRoutev6(String network, String device) { + String[] v6parts = network.split("/"); + boolean included = isAndroidTunDevice(device); + + // Tun is opened after ROUTE6, no device name may be present + + try { + Inet6Address ip = (Inet6Address) InetAddress.getAllByName(v6parts[0])[0]; + int mask = Integer.parseInt(v6parts[1]); + mRoutesv6.addIPv6(ip, mask, included); + + } catch (UnknownHostException e) { + VpnStatus.logException(e); + } + + + } + + private boolean isAndroidTunDevice(String device) { + return device!=null && + (device.startsWith("tun") || "(null)".equals(device) || "vpnservice-tun".equals(device)); + } + + public void setMtu(int mtu) { + mMtu = mtu; + } + + public void setLocalIP(CIDRIP cdrip) { + mLocalIP = cdrip; + } + + public void setLocalIP(String local, String netmask, int mtu, String mode) { + mLocalIP = new CIDRIP(local, netmask); + mMtu = mtu; + mRemoteGW=null; + + + if (mLocalIP.len == 32 && !netmask.equals("255.255.255.255")) { + // get the netmask as IP + long netMaskAsInt = CIDRIP.getInt(netmask); + + int masklen; + if ("net30".equals(mode)) + masklen = 30; + else + masklen = 31; + + int mask = ~( 1 << (32 - (mLocalIP.len +1))); + // Netmask is Ip address +/-1, assume net30/p2p with small net + if ((netMaskAsInt & mask) == (mLocalIP.getInt() & mask )) { + mLocalIP.len = masklen; + } else { + if (!"p2p".equals(mode)) + VpnStatus.logWarning(R.string.ip_not_cidr, local, netmask, mode); + mRemoteGW=netmask; + } + } + } + + public void setLocalIPv6(String ipv6addr) { + mLocalIPv6 = ipv6addr; + } + + @Override + public void updateState(String state, String logmessage, int resid, ConnectionStatus level) { + // If the process is not running, ignore any state, + // Notification should be invisible in this state + doSendBroadcast(state, level); + if (mProcessThread == null && !mNotificationAlwaysVisible) + return; + + boolean lowpriority = false; + // Display byte count only after being connected + + { + if (level == LEVEL_WAITING_FOR_USER_INPUT) { + // The user is presented a dialog of some kind, no need to inform the user + // with a notifcation + return; + } else if (level == LEVEL_CONNECTED) { + mDisplayBytecount = true; + mConnecttime = System.currentTimeMillis(); + lowpriority = true; + } else { + mDisplayBytecount = false; + } + + // Other notifications are shown, + // This also mean we are no longer connected, ignore bytecount messages until next + // CONNECTED + // Does not work :( + String msg = getString(resid); + String ticker = msg; + showNotification(msg + " " + logmessage, ticker, lowpriority , 0, level); + + } + } + + private void doSendBroadcast(String state, ConnectionStatus level) { + Intent vpnstatus = new Intent(); + vpnstatus.setAction("de.blinkt.openvpn.VPN_STATUS"); + vpnstatus.putExtra("status", level.toString()); + vpnstatus.putExtra("detailstatus", state); + sendBroadcast(vpnstatus, permission.ACCESS_NETWORK_STATE); + } + + @Override + public void updateByteCount(long in, long out, long diffIn, long diffOut) { + if (mDisplayBytecount) { + String netstat = String.format(getString(R.string.statusline_bytecount), + humanReadableByteCount(in, false), + humanReadableByteCount(diffIn / OpenVPNManagement.mBytecountInterval, true), + humanReadableByteCount(out, false), + humanReadableByteCount(diffOut / OpenVPNManagement.mBytecountInterval, true)); + + boolean lowpriority = !mNotificationAlwaysVisible; + showNotification(netstat, null, lowpriority, mConnecttime, LEVEL_CONNECTED); + } + + } + + @Override + public boolean handleMessage(Message msg) { + Runnable r = msg.getCallback(); + if (r != null) { + r.run(); + return true; + } else { + return false; + } + } + + public OpenVPNManagement getManagement() { + return mManagement; + } + + public String getTunReopenStatus() { + String currentConfiguration = getTunConfigString(); + if (currentConfiguration.equals(mLastTunCfg)) + return "NOACTION"; + else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) + return "OPEN_AFTER_CLOSE"; + else + return "OPEN_BEFORE_CLOSE"; + } + + public class LocalBinder extends Binder { + public OpenVpnService getService() { + // Return this instance of LocalService so clients can call public methods + return OpenVpnService.this; + } + } +} diff --git a/app/src/main/java/de/blinkt/openvpn/core/PRNGFixes.java b/app/src/main/java/de/blinkt/openvpn/core/PRNGFixes.java new file mode 100644 index 00000000..dd420371 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/core/PRNGFixes.java @@ -0,0 +1,334 @@ +package de.blinkt.openvpn.core;/* + * This software is provided 'as-is', without any express or implied + * warranty. In no event will Google be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, as long as the origin is not misrepresented. + */ + +import android.os.Build; +import android.os.Process; +import android.util.Log; + +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.SecureRandom; +import java.security.SecureRandomSpi; +import java.security.Security; + +/** + * Fixes for the output of the default PRNG having low entropy. + * + * The fixes need to be applied via {@link #apply()} before any use of Java + * Cryptography Architecture primitives. A good place to invoke them is in the + * application's {@code onCreate}. + */ +public final class PRNGFixes { + + private static final int VERSION_CODE_JELLY_BEAN = 16; + private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18; + private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL = + getBuildFingerprintAndDeviceSerial(); + + /** Hidden constructor to prevent instantiation. */ + private PRNGFixes() {} + + /** + * Applies all fixes. + * + * @throws SecurityException if a fix is needed but could not be applied. + */ + public static void apply() { + applyOpenSSLFix(); + installLinuxPRNGSecureRandom(); + } + + /** + * Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the + * fix is not needed. + * + * @throws SecurityException if the fix is needed but could not be applied. + */ + private static void applyOpenSSLFix() throws SecurityException { + if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN) + || (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) { + // No need to apply the fix + return; + } + + try { + // Mix in the device- and invocation-specific seed. + Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto") + .getMethod("RAND_seed", byte[].class) + .invoke(null, generateSeed()); + + // Mix output of Linux PRNG into OpenSSL's PRNG + int bytesRead = (Integer) Class.forName( + "org.apache.harmony.xnet.provider.jsse.NativeCrypto") + .getMethod("RAND_load_file", String.class, long.class) + .invoke(null, "/dev/urandom", 1024); + if (bytesRead != 1024) { + throw new IOException( + "Unexpected number of bytes read from Linux PRNG: " + + bytesRead); + } + } catch (Exception e) { + throw new SecurityException("Failed to seed OpenSSL PRNG", e); + } + } + + /** + * Installs a Linux PRNG-backed {@code SecureRandom} implementation as the + * default. Does nothing if the implementation is already the default or if + * there is not need to install the implementation. + * + * @throws SecurityException if the fix is needed but could not be applied. + */ + private static void installLinuxPRNGSecureRandom() + throws SecurityException { + if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) { + // No need to apply the fix + return; + } + + // Install a Linux PRNG-based SecureRandom implementation as the + // default, if not yet installed. + Provider[] secureRandomProviders = + Security.getProviders("SecureRandom.SHA1PRNG"); + if ((secureRandomProviders == null) + || (secureRandomProviders.length < 1) + || (!LinuxPRNGSecureRandomProvider.class.equals( + secureRandomProviders[0].getClass()))) { + Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1); + } + + // Assert that new SecureRandom() and + // SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed + // by the Linux PRNG-based SecureRandom implementation. + SecureRandom rng1 = new SecureRandom(); + if (!LinuxPRNGSecureRandomProvider.class.equals( + rng1.getProvider().getClass())) { + throw new SecurityException( + "new SecureRandom() backed by wrong Provider: " + + rng1.getProvider().getClass()); + } + + SecureRandom rng2; + try { + rng2 = SecureRandom.getInstance("SHA1PRNG"); + } catch (NoSuchAlgorithmException e) { + throw new SecurityException("SHA1PRNG not available", e); + } + if (!LinuxPRNGSecureRandomProvider.class.equals( + rng2.getProvider().getClass())) { + throw new SecurityException( + "SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong" + + " Provider: " + rng2.getProvider().getClass()); + } + } + + /** + * {@code Provider} of {@code SecureRandom} engines which pass through + * all requests to the Linux PRNG. + */ + private static class LinuxPRNGSecureRandomProvider extends Provider { + + public LinuxPRNGSecureRandomProvider() { + super("LinuxPRNG", + 1.0, + "A Linux-specific random number provider that uses" + + " /dev/urandom"); + // Although /dev/urandom is not a SHA-1 PRNG, some apps + // explicitly request a SHA1PRNG SecureRandom and we thus need to + // prevent them from getting the default implementation whose output + // may have low entropy. + put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName()); + put("SecureRandom.SHA1PRNG ImplementedIn", "Software"); + } + } + + /** + * {@link SecureRandomSpi} which passes all requests to the Linux PRNG + * ({@code /dev/urandom}). + */ + public static class LinuxPRNGSecureRandom extends SecureRandomSpi { + + /* + * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed + * are passed through to the Linux PRNG (/dev/urandom). Instances of + * this class seed themselves by mixing in the current time, PID, UID, + * build fingerprint, and hardware serial number (where available) into + * Linux PRNG. + * + * Concurrency: Read requests to the underlying Linux PRNG are + * serialized (on sLock) to ensure that multiple threads do not get + * duplicated PRNG output. + */ + + private static final File URANDOM_FILE = new File("/dev/urandom"); + + private static final Object sLock = new Object(); + + /** + * Input stream for reading from Linux PRNG or {@code null} if not yet + * opened. + * + * @GuardedBy("sLock") + */ + private static DataInputStream sUrandomIn; + + /** + * Output stream for writing to Linux PRNG or {@code null} if not yet + * opened. + * + * @GuardedBy("sLock") + */ + private static OutputStream sUrandomOut; + + /** + * Whether this engine instance has been seeded. This is needed because + * each instance needs to seed itself if the client does not explicitly + * seed it. + */ + private boolean mSeeded; + + @Override + protected void engineSetSeed(byte[] bytes) { + try { + OutputStream out; + synchronized (sLock) { + out = getUrandomOutputStream(); + } + out.write(bytes); + out.flush(); + } catch (IOException e) { + // On a small fraction of devices /dev/urandom is not writable. + // Log and ignore. + Log.w(PRNGFixes.class.getSimpleName(), + "Failed to mix seed into " + URANDOM_FILE); + } finally { + mSeeded = true; + } + } + + @Override + protected void engineNextBytes(byte[] bytes) { + if (!mSeeded) { + // Mix in the device- and invocation-specific seed. + engineSetSeed(generateSeed()); + } + + try { + DataInputStream in; + synchronized (sLock) { + in = getUrandomInputStream(); + } + synchronized (in) { + in.readFully(bytes); + } + } catch (IOException e) { + throw new SecurityException( + "Failed to read from " + URANDOM_FILE, e); + } + } + + @Override + protected byte[] engineGenerateSeed(int size) { + byte[] seed = new byte[size]; + engineNextBytes(seed); + return seed; + } + + private DataInputStream getUrandomInputStream() { + synchronized (sLock) { + if (sUrandomIn == null) { + // NOTE: Consider inserting a BufferedInputStream between + // DataInputStream and FileInputStream if you need higher + // PRNG output performance and can live with future PRNG + // output being pulled into this process prematurely. + try { + sUrandomIn = new DataInputStream( + new FileInputStream(URANDOM_FILE)); + } catch (IOException e) { + throw new SecurityException("Failed to open " + + URANDOM_FILE + " for reading", e); + } + } + return sUrandomIn; + } + } + + private OutputStream getUrandomOutputStream() throws IOException { + synchronized (sLock) { + if (sUrandomOut == null) { + sUrandomOut = new FileOutputStream(URANDOM_FILE); + } + return sUrandomOut; + } + } + } + + /** + * Generates a device- and invocation-specific seed to be mixed into the + * Linux PRNG. + */ + private static byte[] generateSeed() { + try { + ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream(); + DataOutputStream seedBufferOut = + new DataOutputStream(seedBuffer); + seedBufferOut.writeLong(System.currentTimeMillis()); + seedBufferOut.writeLong(System.nanoTime()); + seedBufferOut.writeInt(Process.myPid()); + seedBufferOut.writeInt(Process.myUid()); + seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL); + seedBufferOut.close(); + return seedBuffer.toByteArray(); + } catch (IOException e) { + throw new SecurityException("Failed to generate seed", e); + } + } + + /** + * Gets the hardware serial number of this device. + * + * @return serial number or {@code null} if not available. + */ + private static String getDeviceSerialNumber() { + // We're using the Reflection API because Build.SERIAL is only available + // since API Level 9 (Gingerbread, Android 2.3). + try { + return (String) Build.class.getField("SERIAL").get(null); + } catch (Exception ignored) { + return null; + } + } + + private static byte[] getBuildFingerprintAndDeviceSerial() { + StringBuilder result = new StringBuilder(); + String fingerprint = Build.FINGERPRINT; + if (fingerprint != null) { + result.append(fingerprint); + } + String serial = getDeviceSerialNumber(); + if (serial != null) { + result.append(serial); + } + try { + return result.toString().getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("UTF-8 encoding not supported"); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/de/blinkt/openvpn/core/ProfileManager.java b/app/src/main/java/de/blinkt/openvpn/core/ProfileManager.java new file mode 100644 index 00000000..4cfbcc8e --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/core/ProfileManager.java @@ -0,0 +1,222 @@ +package de.blinkt.openvpn.core; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.StreamCorruptedException; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +import de.blinkt.openvpn.VpnProfile; + +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.preference.PreferenceManager; + +public class ProfileManager { + private static final String PREFS_NAME = "VPNList"; + + + + private static final String ONBOOTPROFILE = "onBootProfile"; + + + + private static ProfileManager instance; + + + + private static VpnProfile mLastConnectedVpn=null; + private HashMap profiles=new HashMap(); + private static VpnProfile tmpprofile=null; + + + private static VpnProfile get(String key) { + if (tmpprofile!=null && tmpprofile.getUUIDString().equals(key)) + return tmpprofile; + + if(instance==null) + return null; + return instance.profiles.get(key); + + } + + + + private ProfileManager() { } + + private static void checkInstance(Context context) { + if(instance == null) { + instance = new ProfileManager(); + instance.loadVPNList(context); + } + } + + synchronized public static ProfileManager getInstance(Context context) { + checkInstance(context); + return instance; + } + + public static void setConntectedVpnProfileDisconnected(Context c) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); + Editor prefsedit = prefs.edit(); + prefsedit.putString(ONBOOTPROFILE, null); + prefsedit.apply(); + + } + + public static void setConnectedVpnProfile(Context c, VpnProfile connectedrofile) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); + Editor prefsedit = prefs.edit(); + + prefsedit.putString(ONBOOTPROFILE, connectedrofile.getUUIDString()); + prefsedit.apply(); + mLastConnectedVpn=connectedrofile; + + } + + public static VpnProfile getOnBootProfile(Context c) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); + + boolean useStartOnBoot = prefs.getBoolean("restartvpnonboot", false); + + + String mBootProfileUUID = prefs.getString(ONBOOTPROFILE,null); + if(useStartOnBoot && mBootProfileUUID!=null) + return get(c, mBootProfileUUID); + else + return null; + } + + + + + public Collection getProfiles() { + return profiles.values(); + } + + public VpnProfile getProfileByName(String name) { + for (VpnProfile vpnp : profiles.values()) { + if(vpnp.getName().equals(name)) { + return vpnp; + } + } + return null; + } + + public void saveProfileList(Context context) { + SharedPreferences sharedprefs = context.getSharedPreferences(PREFS_NAME,Activity.MODE_PRIVATE); + Editor editor = sharedprefs.edit(); + editor.putStringSet("vpnlist", profiles.keySet()); + + // For reasing I do not understand at all + // Android saves my prefs file only one time + // if I remove the debug code below :( + int counter = sharedprefs.getInt("counter", 0); + editor.putInt("counter", counter+1); + editor.apply(); + + } + + public void addProfile(VpnProfile profile) { + profiles.put(profile.getUUID().toString(),profile); + + } + + public static void setTemporaryProfile(VpnProfile tmp) { + ProfileManager.tmpprofile = tmp; + } + + + public void saveProfile(Context context,VpnProfile profile) { + // First let basic settings save its state + + ObjectOutputStream vpnfile; + try { + vpnfile = new ObjectOutputStream(context.openFileOutput((profile.getUUID().toString() + ".vp"),Activity.MODE_PRIVATE)); + + vpnfile.writeObject(profile); + vpnfile.flush(); + vpnfile.close(); + } catch (FileNotFoundException e) { + + VpnStatus.logException("saving VPN profile", e); + throw new RuntimeException(e); + } catch (IOException e) { + VpnStatus.logException("saving VPN profile", e); + throw new RuntimeException(e); + } + } + + + private void loadVPNList(Context context) { + profiles = new HashMap(); + SharedPreferences listpref = context.getSharedPreferences(PREFS_NAME,Activity.MODE_PRIVATE); + Set vlist = listpref.getStringSet("vpnlist", null); + Exception exp =null; + if(vlist==null){ + vlist = new HashSet(); + } + + for (String vpnentry : vlist) { + try { + ObjectInputStream vpnfile = new ObjectInputStream(context.openFileInput(vpnentry + ".vp")); + VpnProfile vp = ((VpnProfile) vpnfile.readObject()); + + // Sanity check + if(vp==null || vp.mName==null || vp.getUUID()==null) + continue; + + vp.upgradeProfile(); + profiles.put(vp.getUUID().toString(), vp); + + } catch (StreamCorruptedException e) { + exp=e; + } catch (FileNotFoundException e) { + exp=e; + } catch (IOException e) { + exp=e; + } catch (ClassNotFoundException e) { + exp=e; + } + if(exp!=null) { + VpnStatus.logException("Loading VPN List",exp); + } + } + } + + public int getNumberOfProfiles() { + return profiles.size(); + } + + + + public void removeProfile(Context context,VpnProfile profile) { + String vpnentry = profile.getUUID().toString(); + profiles.remove(vpnentry); + saveProfileList(context); + context.deleteFile(vpnentry + ".vp"); + if(mLastConnectedVpn==profile) + mLastConnectedVpn=null; + + } + + + + public static VpnProfile get(Context context, String profileUUID) { + checkInstance(context); + return get(profileUUID); + } + + + + public static VpnProfile getLastConnectedVpn() { + return mLastConnectedVpn; + } + +} diff --git a/app/src/main/java/de/blinkt/openvpn/core/ProxyDetection.java b/app/src/main/java/de/blinkt/openvpn/core/ProxyDetection.java new file mode 100644 index 00000000..4f66c503 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/core/ProxyDetection.java @@ -0,0 +1,55 @@ +package de.blinkt.openvpn.core; + +import java.net.InetSocketAddress; +import java.net.MalformedURLException; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.SocketAddress; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.List; + +import de.blinkt.openvpn.R; +import de.blinkt.openvpn.VpnProfile; + +public class ProxyDetection { + static SocketAddress detectProxy(VpnProfile vp) { + // Construct a new url with https as protocol + try { + URL url = new URL(String.format("https://%s:%s",vp.mServerName,vp.mServerPort)); + Proxy proxy = getFirstProxy(url); + + if(proxy==null) + return null; + SocketAddress addr = proxy.address(); + if (addr instanceof InetSocketAddress) { + return addr; + } + + } catch (MalformedURLException e) { + VpnStatus.logError(R.string.getproxy_error, e.getLocalizedMessage()); + } catch (URISyntaxException e) { + VpnStatus.logError(R.string.getproxy_error, e.getLocalizedMessage()); + } + return null; + } + + static Proxy getFirstProxy(URL url) throws URISyntaxException { + System.setProperty("java.net.useSystemProxies", "true"); + + List proxylist = ProxySelector.getDefault().select(url.toURI()); + + + if (proxylist != null) { + for (Proxy proxy: proxylist) { + SocketAddress addr = proxy.address(); + + if (addr != null) { + return proxy; + } + } + + } + return null; + } +} \ No newline at end of file diff --git a/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java b/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java new file mode 100644 index 00000000..5f1efb5f --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java @@ -0,0 +1,77 @@ +package de.blinkt.openvpn.core; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import de.blinkt.openvpn.R; +import de.blinkt.openvpn.VpnProfile; + +public class VPNLaunchHelper { + static private boolean writeMiniVPN(Context context) { + File mvpnout = new File(context.getCacheDir(),VpnProfile.MINIVPN); + if (mvpnout.exists() && mvpnout.canExecute()) + return true; + + IOException e2 = null; + + try { + InputStream mvpn; + + try { + mvpn = context.getAssets().open("minivpn." + Build.CPU_ABI); + } + catch (IOException errabi) { + VpnStatus.logInfo("Failed getting assets for archicture " + Build.CPU_ABI); + e2=errabi; + mvpn = context.getAssets().open("minivpn." + Build.CPU_ABI2); + + } + + + FileOutputStream fout = new FileOutputStream(mvpnout); + + byte buf[]= new byte[4096]; + + int lenread = mvpn.read(buf); + while(lenread> 0) { + fout.write(buf, 0, lenread); + lenread = mvpn.read(buf); + } + fout.close(); + + if(!mvpnout.setExecutable(true)) { + VpnStatus.logError("Failed to set minivpn executable"); + return false; + } + + + return true; + } catch (IOException e) { + if(e2!=null) + VpnStatus.logException(e2); + VpnStatus.logException(e); + + return false; + } + } + + + public static void startOpenVpn(VpnProfile startprofile, Context context) { + if(!writeMiniVPN(context)) { + VpnStatus.logError("Error writing minivpn binary"); + return; + } + + VpnStatus.logInfo(R.string.building_configration); + + Intent startVPN = startprofile.prepareIntent(context); + if(startVPN!=null) + context.startService(startVPN); + + } +} diff --git a/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java b/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java new file mode 100644 index 00000000..d146aef8 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java @@ -0,0 +1,540 @@ +package de.blinkt.openvpn.core; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.Signature; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; +import de.blinkt.openvpn.R; + +import java.io.ByteArrayInputStream; +import java.io.FileNotFoundException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.FormatFlagsConversionMismatchException; +import java.util.LinkedList; +import java.util.Locale; +import java.util.UnknownFormatConversionException; +import java.util.Vector; + +public class VpnStatus { + + + public static LinkedList logbuffer; + + private static Vector logListener; + private static Vector stateListener; + private static Vector byteCountListener; + + private static String mLaststatemsg=""; + + private static String mLaststate = "NOPROCESS"; + + private static int mLastStateresid=R.string.state_noprocess; + + private static long mlastByteCount[]={0,0,0,0}; + + public static void logException(LogLevel ll, String context, Exception e) { + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw)); + LogItem li; + if (context !=null) { + li = new LogItem(ll, R.string.unhandled_exception_context, e.getMessage(), sw.toString(), context); + } else { + li = new LogItem(ll, R.string.unhandled_exception, e.getMessage(), sw.toString()); + } + newLogItem(li); + } + + public static void logException(Exception e) { + logException(LogLevel.ERROR, null, e); + } + + public static void logException(String context, Exception e) { + logException(LogLevel.ERROR, context, e); + } + + private static final int MAXLOGENTRIES = 1000; + + public static final String MANAGMENT_PREFIX = "M:"; + + public enum ConnectionStatus { + LEVEL_CONNECTED, + LEVEL_VPNPAUSED, + LEVEL_CONNECTING_SERVER_REPLIED, + LEVEL_CONNECTING_NO_SERVER_REPLY_YET, + LEVEL_NONETWORK, + LEVEL_NOTCONNECTED, + LEVEL_AUTH_FAILED, + LEVEL_WAITING_FOR_USER_INPUT, + UNKNOWN_LEVEL + } + + public enum LogLevel { + INFO(2), + ERROR(-2), + WARNING(1), + VERBOSE(3), + DEBUG(4); + + protected int mValue; + LogLevel(int value) { + mValue = value; + } + + public int getInt() { + return mValue; + } + + public static LogLevel getEnumByValue(int value) { + switch (value) { + case 1: return INFO; + case 2: return ERROR; + case 3: return WARNING; + case 4: return DEBUG; + default: return null; + } + } + } + + // keytool -printcert -jarfile de.blinkt.openvpn_85.apk + public static final byte[] officalkey = {-58, -42, -44, -106, 90, -88, -87, -88, -52, -124, 84, 117, 66, 79, -112, -111, -46, 86, -37, 109}; + public static final byte[] officaldebugkey = {-99, -69, 45, 71, 114, -116, 82, 66, -99, -122, 50, -70, -56, -111, 98, -35, -65, 105, 82, 43}; + public static final byte[] amazonkey = {-116, -115, -118, -89, -116, -112, 120, 55, 79, -8, -119, -23, 106, -114, -85, -56, -4, 105, 26, -57}; + public static final byte[] fdroidkey = {-92, 111, -42, -46, 123, -96, -60, 79, -27, -31, 49, 103, 11, -54, -68, -27, 17, 2, 121, 104}; + + + private static ConnectionStatus mLastLevel=ConnectionStatus.LEVEL_NOTCONNECTED; + + static { + logbuffer = new LinkedList(); + logListener = new Vector(); + stateListener = new Vector(); + byteCountListener = new Vector(); + logInformation(); + } + + + public static class LogItem implements Parcelable { + + + private Object [] mArgs = null; + private String mMessage = null; + private int mRessourceId; + // Default log priority + LogLevel mLevel = LogLevel.INFO; + private long logtime = System.currentTimeMillis(); + private int mVerbosityLevel = -1; + + private LogItem(int ressourceId, Object[] args) { + mRessourceId = ressourceId; + mArgs = args; + } + + public LogItem(LogLevel level, int verblevel, String message) { + mMessage=message; + mLevel = level; + mVerbosityLevel = verblevel; + } + + @Override + public int describeContents() { + return 0; + } + + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeArray(mArgs); + dest.writeString(mMessage); + dest.writeInt(mRessourceId); + dest.writeInt(mLevel.getInt()); + dest.writeInt(mVerbosityLevel); + + dest.writeLong(logtime); + } + + public LogItem(Parcel in) { + mArgs = in.readArray(Object.class.getClassLoader()); + mMessage = in.readString(); + mRessourceId = in.readInt(); + mLevel = LogLevel.getEnumByValue(in.readInt()); + mVerbosityLevel = in.readInt(); + logtime = in.readLong(); + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public LogItem createFromParcel(Parcel in) { + return new LogItem(in); + } + + public LogItem[] newArray(int size) { + return new LogItem[size]; + } + }; + + public LogItem(LogLevel loglevel,int ressourceId, Object... args) { + mRessourceId = ressourceId; + mArgs =args; + mLevel = loglevel; + } + + + public LogItem(LogLevel loglevel, String msg) { + mLevel = loglevel; + mMessage = msg; + } + + + public LogItem(LogLevel loglevel, int ressourceId) { + mRessourceId =ressourceId; + mLevel = loglevel; + } + + public String getString(Context c) { + try { + if(mMessage !=null) { + return mMessage; + } else { + if(c!=null) { + if(mRessourceId==R.string.mobile_info) + return getMobileInfoString(c); + if(mArgs == null) + return c.getString(mRessourceId); + else + return c.getString(mRessourceId,mArgs); + } else { + String str = String.format(Locale.ENGLISH,"Log (no context) resid %d", mRessourceId); + if(mArgs !=null) + for(Object o:mArgs) + str += "|" + o.toString(); + + return str; + } + } + } catch (UnknownFormatConversionException e) { + if (c != null) + throw new UnknownFormatConversionException(e.getLocalizedMessage() + getString(null)); + else + throw e; + } catch (java.util.FormatFlagsConversionMismatchException e) { + if (c != null) + throw new FormatFlagsConversionMismatchException(e.getLocalizedMessage() + getString(null),e.getConversion()); + else + throw e; + } + + } + + public LogLevel getLogLevel() + { + return mLevel; + } + + // The lint is wrong here + @SuppressLint("StringFormatMatches") + private String getMobileInfoString(Context c) { + c.getPackageManager(); + String apksign="error getting package signature"; + + String version="error getting version"; + try { + Signature raw = c.getPackageManager().getPackageInfo(c.getPackageName(), PackageManager.GET_SIGNATURES).signatures[0]; + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(raw.toByteArray())); + MessageDigest md = MessageDigest.getInstance("SHA-1"); + byte[] der = cert.getEncoded(); + md.update(der); + byte[] digest = md.digest(); + + if (Arrays.equals(digest, officalkey)) + apksign = c.getString(R.string.official_build); + else if (Arrays.equals(digest, officaldebugkey)) + apksign = c.getString(R.string.debug_build); + else if (Arrays.equals(digest, amazonkey)) + apksign = "amazon version"; + else if (Arrays.equals(digest, fdroidkey)) + apksign = "F-Droid built and signed version"; + else + apksign = c.getString(R.string.built_by,cert.getSubjectX500Principal().getName()); + + PackageInfo packageinfo = c.getPackageManager().getPackageInfo(c.getPackageName(), 0); + version = packageinfo.versionName; + + } catch (NameNotFoundException e) { + } catch (CertificateException e) { + } catch (NoSuchAlgorithmException e) { + } + + Object[] argsext = Arrays.copyOf(mArgs, mArgs.length+2); + argsext[argsext.length-1]=apksign; + argsext[argsext.length-2]=version; + + return c.getString(R.string.mobile_info_extended, argsext); + + } + + public long getLogtime() { + return logtime; + } + + + public int getVerbosityLevel() { + if (mVerbosityLevel==-1) { + // Hack: + // For message not from OpenVPN, report the status level as log level + return mLevel.getInt(); + } + return mVerbosityLevel; + } + } + + + + public interface LogListener { + void newLog(LogItem logItem); + } + + public interface StateListener { + void updateState(String state, String logmessage, int localizedResId, ConnectionStatus level); + } + + public interface ByteCountListener { + void updateByteCount(long in, long out, long diffIn, long diffOut); + } + + public synchronized static void logMessage(LogLevel level,String prefix, String message) + { + newLogItem(new LogItem(level, prefix + message)); + + } + + public synchronized static void clearLog() { + logbuffer.clear(); + logInformation(); + } + + private static void logInformation() { + logInfo(R.string.mobile_info,Build.MODEL, Build.BOARD,Build.BRAND,Build.VERSION.SDK_INT); + } + + public synchronized static void addLogListener(LogListener ll){ + logListener.add(ll); + } + + public synchronized static void removeLogListener(LogListener ll) { + logListener.remove(ll); + } + + public synchronized static void addByteCountListener(ByteCountListener bcl) { + bcl.updateByteCount(mlastByteCount[0], mlastByteCount[1], mlastByteCount[2], mlastByteCount[3]); + byteCountListener.add(bcl); + } + + public synchronized static void removeByteCountListener(ByteCountListener bcl) { + byteCountListener.remove(bcl); + } + + + public synchronized static void addStateListener(StateListener sl){ + if(!stateListener.contains(sl)){ + stateListener.add(sl); + if(mLaststate!=null) + sl.updateState(mLaststate, mLaststatemsg, mLastStateresid, mLastLevel); + } + } + + private static int getLocalizedState(String state){ + if (state.equals("CONNECTING")) + return R.string.state_connecting; + else if (state.equals("WAIT")) + return R.string.state_wait; + else if (state.equals("AUTH")) + return R.string.state_auth; + else if (state.equals("GET_CONFIG")) + return R.string.state_get_config; + else if (state.equals("ASSIGN_IP")) + return R.string.state_assign_ip; + else if (state.equals("ADD_ROUTES")) + return R.string.state_add_routes; + else if (state.equals("CONNECTED")) + return R.string.state_connected; + else if (state.equals("DISCONNECTED")) + return R.string.state_disconnected; + else if (state.equals("RECONNECTING")) + return R.string.state_reconnecting; + else if (state.equals("EXITING")) + return R.string.state_exiting; + else if (state.equals("RESOLVE")) + return R.string.state_resolve; + else if (state.equals("TCP_CONNECT")) + return R.string.state_tcp_connect; + else + return R.string.unknown_state; + + } + + public static void updateStatePause(OpenVPNManagement.pauseReason pauseReason) { + switch (pauseReason) { + case noNetwork: + VpnStatus.updateStateString("NONETWORK", "", R.string.state_nonetwork, ConnectionStatus.LEVEL_NONETWORK); + break; + case screenOff: + VpnStatus.updateStateString("SCREENOFF", "", R.string.state_screenoff, ConnectionStatus.LEVEL_VPNPAUSED); + break; + case userPause: + VpnStatus.updateStateString("USERPAUSE", "", R.string.state_userpause, ConnectionStatus.LEVEL_VPNPAUSED); + break; + } + + } + + private static ConnectionStatus getLevel(String state){ + String[] noreplyet = {"CONNECTING","WAIT", "RECONNECTING", "RESOLVE", "TCP_CONNECT"}; + String[] reply = {"AUTH","GET_CONFIG","ASSIGN_IP","ADD_ROUTES"}; + String[] connected = {"CONNECTED"}; + String[] notconnected = {"DISCONNECTED", "EXITING"}; + + for(String x:noreplyet) + if(state.equals(x)) + return ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET; + + for(String x:reply) + if(state.equals(x)) + return ConnectionStatus.LEVEL_CONNECTING_SERVER_REPLIED; + + for(String x:connected) + if(state.equals(x)) + return ConnectionStatus.LEVEL_CONNECTED; + + for(String x:notconnected) + if(state.equals(x)) + return ConnectionStatus.LEVEL_NOTCONNECTED; + + return ConnectionStatus.UNKNOWN_LEVEL; + + } + + + + + public synchronized static void removeStateListener(StateListener sl) { + stateListener.remove(sl); + } + + + synchronized public static LogItem[] getlogbuffer() { + + // The stoned way of java to return an array from a vector + // brought to you by eclipse auto complete + return logbuffer.toArray(new LogItem[logbuffer.size()]); + + } + + public static void updateStateString (String state, String msg) { + int rid = getLocalizedState(state); + ConnectionStatus level = getLevel(state); + updateStateString(state, msg, rid, level); + } + + public synchronized static void updateStateString(String state, String msg, int resid, ConnectionStatus level) { + // Workound for OpenVPN doing AUTH and wait and being connected + // Simply ignore these state + if (mLastLevel == ConnectionStatus.LEVEL_CONNECTED && + (state.equals("WAIT") || state.equals("AUTH"))) + { + newLogItem(new LogItem((LogLevel.DEBUG), String.format("Ignoring OpenVPN Status in CONNECTED state (%s->%s): %s",state,level.toString(),msg))); + return; + } + + mLaststate= state; + mLaststatemsg = msg; + mLastStateresid = resid; + mLastLevel = level; + + + + for (StateListener sl : stateListener) { + sl.updateState(state,msg,resid,level); + } + //newLogItem(new LogItem((LogLevel.DEBUG), String.format("New OpenVPN Status (%s->%s): %s",state,level.toString(),msg))); + } + + public static void logInfo(String message) { + newLogItem(new LogItem(LogLevel.INFO, message)); + } + + public static void logInfo(int resourceId, Object... args) { + newLogItem(new LogItem(LogLevel.INFO, resourceId, args)); + } + + public static void logDebug(int resourceId, Object... args) { + newLogItem(new LogItem(LogLevel.DEBUG, resourceId, args)); + } + + + private synchronized static void newLogItem(LogItem logItem) { + logbuffer.addLast(logItem); + if(logbuffer.size()>MAXLOGENTRIES) + logbuffer.removeFirst(); + + for (LogListener ll : logListener) { + ll.newLog(logItem); + } + } + + public static void logError(String msg) { + newLogItem(new LogItem(LogLevel.ERROR, msg)); + + } + + public static void logWarning(int resourceId, Object... args) { + newLogItem(new LogItem(LogLevel.WARNING, resourceId, args)); + } + + public static void logWarning(String msg) { + newLogItem(new LogItem(LogLevel.WARNING, msg)); + } + + + public static void logError(int resourceId) { + newLogItem(new LogItem(LogLevel.ERROR, resourceId)); + } + public static void logError(int resourceId, Object... args) { + newLogItem(new LogItem(LogLevel.ERROR, resourceId, args)); + } + + public static void logMessageOpenVPN(LogLevel level, int ovpnlevel, String message) { + newLogItem(new LogItem(level, ovpnlevel, message)); + + } + + + public static synchronized void updateByteCount(long in, long out) { + long lastIn = mlastByteCount[0]; + long lastOut = mlastByteCount[1]; + long diffIn = mlastByteCount[2] = in - lastIn; + long diffOut = mlastByteCount[3] = out - lastOut; + + + + mlastByteCount = new long[] {in,out,diffIn,diffOut}; + for(ByteCountListener bcl:byteCountListener){ + bcl.updateByteCount(in, out, diffIn,diffOut); + } + } + + + +} diff --git a/app/src/main/java/de/blinkt/openvpn/core/X509Utils.java b/app/src/main/java/de/blinkt/openvpn/core/X509Utils.java new file mode 100644 index 00000000..da1e4ed5 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/core/X509Utils.java @@ -0,0 +1,155 @@ +package de.blinkt.openvpn.core; + +import android.content.Context; +import android.text.TextUtils; + +import de.blinkt.openvpn.R; +import de.blinkt.openvpn.VpnProfile; +import org.spongycastle.util.io.pem.PemObject; +import org.spongycastle.util.io.pem.PemReader; + + +import javax.security.auth.x500.X500Principal; +import java.io.*; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Hashtable; + +public class X509Utils { + public static Certificate getCertificateFromFile(String certfilename) throws FileNotFoundException, CertificateException { + CertificateFactory certFact = CertificateFactory.getInstance("X.509"); + + InputStream inStream; + + if(VpnProfile.isEmbedded(certfilename)) { + // The java certifcate reader is ... kind of stupid + // It does NOT ignore chars before the --BEGIN ... + int subIndex = certfilename.indexOf("-----BEGIN CERTIFICATE-----"); + subIndex = Math.max(0,subIndex); + inStream = new ByteArrayInputStream(certfilename.substring(subIndex).getBytes()); + + + } else { + inStream = new FileInputStream(certfilename); + } + + + return certFact.generateCertificate(inStream); + } + + public static PemObject readPemObjectFromFile (String keyfilename) throws IOException { + + Reader inStream; + + if(VpnProfile.isEmbedded(keyfilename)) + inStream = new StringReader(VpnProfile.getEmbeddedContent(keyfilename)); + else + inStream = new FileReader(new File(keyfilename)); + + PemReader pr = new PemReader(inStream); + PemObject r = pr.readPemObject(); + pr.close(); + return r; + } + + + + + public static String getCertificateFriendlyName (Context c, String filename) { + if(!TextUtils.isEmpty(filename)) { + try { + X509Certificate cert = (X509Certificate) getCertificateFromFile(filename); + + return getCertificateFriendlyName(cert); + + } catch (Exception e) { + VpnStatus.logError("Could not read certificate" + e.getLocalizedMessage()); + } + } + return c.getString(R.string.cannotparsecert); + } + + public static String getCertificateFriendlyName(X509Certificate cert) { + X500Principal principal = cert.getSubjectX500Principal(); + byte[] encodedSubject = principal.getEncoded(); + String friendlyName=null; + + /* Hack so we do not have to ship a whole Spongy/bouncycastle */ + Exception exp=null; + try { + Class X509NameClass = Class.forName("com.android.org.bouncycastle.asn1.x509.X509Name"); + Method getInstance = X509NameClass.getMethod("getInstance",Object.class); + + Hashtable defaultSymbols = (Hashtable) X509NameClass.getField("DefaultSymbols").get(X509NameClass); + + if (!defaultSymbols.containsKey("1.2.840.113549.1.9.1")) + defaultSymbols.put("1.2.840.113549.1.9.1","eMail"); + + Object subjectName = getInstance.invoke(X509NameClass, encodedSubject); + + Method toString = X509NameClass.getMethod("toString",boolean.class,Hashtable.class); + + friendlyName= (String) toString.invoke(subjectName,true,defaultSymbols); + + } catch (ClassNotFoundException e) { + exp =e ; + } catch (NoSuchMethodException e) { + exp =e; + } catch (InvocationTargetException e) { + exp =e; + } catch (IllegalAccessException e) { + exp =e; + } catch (NoSuchFieldException e) { + exp =e; + } + if (exp!=null) + VpnStatus.logException("Getting X509 Name from certificate", exp); + + /* Fallback if the reflection method did not work */ + if(friendlyName==null) + friendlyName = principal.getName(); + + + // Really evil hack to decode email address + // See: http://code.google.com/p/android/issues/detail?id=21531 + + String[] parts = friendlyName.split(","); + for (int i=0;i viewToProduct = new Hashtable(); + ServiceConnection mServiceConn = new ServiceConnection() { + @Override + public void onServiceDisconnected(ComponentName name) { + mService = null; + } + + @Override + public void onServiceConnected(ComponentName name, + IBinder service) { + mService = IInAppBillingService.Stub.asInterface(service); + initGooglePlayDonation(); + + } + }; + + private void initGooglePlayDonation() { + new Thread("queryGMSInApp") { + @Override + public void run() { + initGMSDonateOptions(); + } + }.start(); + } + + private TextView gmsTextView; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getActivity().bindService(new + Intent("com.android.vending.billing.InAppBillingService.BIND"), + mServiceConn, Context.BIND_AUTO_CREATE); + + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (mServiceConn != null) { + getActivity().unbindService(mServiceConn); + } + + } + + private void initGMSDonateOptions() { + try { + int billingSupported = mService.isBillingSupported(3, getActivity().getPackageName(), INAPPITEM_TYPE_INAPP); + if (billingSupported != BILLING_RESPONSE_RESULT_OK) { + Log.i("OpenVPN", "Play store billing not supported"); + return; + } + + ArrayList skuList = new ArrayList(); + Collections.addAll(skuList, donationSkus); + Bundle querySkus = new Bundle(); + querySkus.putStringArrayList("ITEM_ID_LIST", skuList); + + Bundle ownedItems = mService.getPurchases(3, getActivity().getPackageName(), INAPPITEM_TYPE_INAPP, null); + + + if (ownedItems.getInt(RESPONSE_CODE) != BILLING_RESPONSE_RESULT_OK) + return; + + final ArrayList ownedSkus = ownedItems.getStringArrayList("INAPP_PURCHASE_ITEM_LIST"); + + Bundle skuDetails = mService.getSkuDetails(3, getActivity().getPackageName(), INAPPITEM_TYPE_INAPP, querySkus); + + + if (skuDetails.getInt(RESPONSE_CODE) != BILLING_RESPONSE_RESULT_OK) + return; + + final ArrayList responseList = skuDetails.getStringArrayList("DETAILS_LIST"); + + if (getActivity() != null) { + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + createPlayBuyOptions(ownedSkus, responseList); + + } + }); + } + + } catch (RemoteException e) { + VpnStatus.logException(e); + } + } + + private static class SkuResponse { + String title; + String price; + + SkuResponse(String p, String t) + { + title=t; + price=p; + } + } + + + + private void createPlayBuyOptions(ArrayList ownedSkus, ArrayList responseList) { + try { + Vector> gdonation = new Vector>(); + + gdonation.add(new Pair(getString(R.string.donatePlayStore),null)); + HashMap responseMap = new HashMap(); + for (String thisResponse : responseList) { + JSONObject object = new JSONObject(thisResponse); + responseMap.put( + object.getString("productId"), + new SkuResponse( + object.getString("price"), + object.getString("title"))); + + } + for (String sku: donationSkus) + if (responseMap.containsKey(sku)) + gdonation.add(getSkuTitle(sku, + responseMap.get(sku).title, responseMap.get(sku).price, ownedSkus)); + + String gmsTextString=""; + for(int i=0;i1) + gmsTextString+= ", "; + gmsTextString+=gdonation.elementAt(i).first; + } + SpannableString gmsText = new SpannableString(gmsTextString); + + + int lStart = 0; + int lEnd=0; + for(Pair item:gdonation){ + lEnd = lStart + item.first.length(); + if (item.second!=null) { + final String mSku = item.second; + ClickableSpan cspan = new ClickableSpan() + { + @Override + public void onClick(View widget) { + triggerBuy(mSku); + } + }; + gmsText.setSpan(cspan,lStart,lEnd,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + lStart = lEnd+2; // Account for ", " between items + } + + if(gmsTextView !=null) { + gmsTextView.setText(gmsText); + gmsTextView.setMovementMethod(LinkMovementMethod.getInstance()); + gmsTextView.setVisibility(View.VISIBLE); + } + + } catch (JSONException e) { + VpnStatus.logException("Parsing Play Store IAP",e); + } + + } + + private Pair getSkuTitle(final String sku, String title, String price, ArrayList ownedSkus) { + String text; + if (ownedSkus.contains(sku)) + return new Pair(getString(R.string.thanks_for_donation, price),null); + + if (price.contains("€")|| price.contains("\u20ac")) { + text= title; + } else { + text = String.format(Locale.getDefault(), "%s (%s)", title, price); + } + //return text; + return new Pair(price, sku); + + } + + private void triggerBuy(String sku) { + try { + Bundle buyBundle + = mService.getBuyIntent(3, getActivity().getPackageName(), + sku, INAPPITEM_TYPE_INAPP, "Thanks for the donation! :)"); + + + if (buyBundle.getInt(RESPONSE_CODE) == BILLING_RESPONSE_RESULT_OK) { + PendingIntent buyIntent = buyBundle.getParcelable(RESPONSE_BUY_INTENT); + getActivity().startIntentSenderForResult(buyIntent.getIntentSender(), DONATION_CODE, new Intent(), + 0, 0, 0); + } + + } catch (RemoteException e) { + VpnStatus.logException(e); + } catch (IntentSender.SendIntentException e) { + VpnStatus.logException(e); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.about, container, false); + TextView ver = (TextView) v.findViewById(R.id.version); + + String version; + String name = "Openvpn"; + try { + PackageInfo packageinfo = getActivity().getPackageManager().getPackageInfo(getActivity().getPackageName(), 0); + version = packageinfo.versionName; + name = getString(R.string.app); + } catch (NameNotFoundException e) { + version = "error fetching version"; + } + + + ver.setText(getString(R.string.version_info, name, version)); + + TextView paypal = (TextView) v.findViewById(R.id.donatestring); + + String donatetext = getActivity().getString(R.string.donatewithpaypal); + Spanned htmltext = Html.fromHtml(donatetext); + paypal.setText(htmltext); + paypal.setMovementMethod(LinkMovementMethod.getInstance()); + gmsTextView = (TextView) v.findViewById(R.id.donategms); + /* recreating view without onCreate/onDestroy cycle */ + if (mService!=null) + initGooglePlayDonation(); + + TextView translation = (TextView) v.findViewById(R.id.translation); + + // Don't print a text for myself + if (getString(R.string.translationby).contains("Arne Schwabe")) + translation.setText(""); + else + translation.setText(R.string.translationby); + + WebView wv = (WebView)v.findViewById(R.id.webView); + wv.loadUrl("file:///android_asset/full_licenses.html"); + + return v; + } + + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + if (mService!=null) + initGooglePlayDonation(); + } + + + @Override + public void onClick(View v) { + + } +} diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/FaqFragment.java b/app/src/main/java/de/blinkt/openvpn/fragments/FaqFragment.java new file mode 100644 index 00000000..238ad952 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/fragments/FaqFragment.java @@ -0,0 +1,36 @@ +package de.blinkt.openvpn.fragments; + +import android.app.Fragment; +import android.os.Bundle; +import android.text.Html; +import android.text.method.LinkMovementMethod; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import de.blinkt.openvpn.R; + +public class FaqFragment extends Fragment { + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View v= inflater.inflate(R.layout.faq, container, false); + + insertHtmlEntry(v,R.id.broken_images_faq,R.string.broken_images_faq); + insertHtmlEntry(v,R.id.faq_howto,R.string.faq_howto); + insertHtmlEntry(v, R.id.baterry_consumption, R.string.baterry_consumption); + insertHtmlEntry(v, R.id.faq_tethering, R.string.faq_tethering); + insertHtmlEntry(v, R.id.faq_vpndialog43, R.string.faq_vpndialog43); + insertHtmlEntry(v, R.id.faq_system_dialog_xposed, R.string.faq_system_dialog_xposed); + return v; + } + + private void insertHtmlEntry (View v, int viewId, int stringId) { + TextView faqitem = (TextView) v.findViewById(viewId); + faqitem.setText(Html.fromHtml(getActivity().getString(stringId))); + faqitem.setMovementMethod(LinkMovementMethod.getInstance()); + + } + +} diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/FileSelectionFragment.java b/app/src/main/java/de/blinkt/openvpn/fragments/FileSelectionFragment.java new file mode 100644 index 00000000..84e065a5 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/fragments/FileSelectionFragment.java @@ -0,0 +1,256 @@ +package de.blinkt.openvpn.fragments; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.TreeMap; + +import android.app.AlertDialog; +import android.app.ListFragment; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.ListView; +import android.widget.SimpleAdapter; +import android.widget.TextView; +import de.blinkt.openvpn.activities.FileSelect; +import de.blinkt.openvpn.R; + +public class FileSelectionFragment extends ListFragment { + + private static final String ITEM_KEY = "key"; + private static final String ITEM_IMAGE = "image"; + private static final String ROOT = "/"; + + + private List path = null; + private TextView myPath; + private ArrayList> mList; + + private Button selectButton; + + + private String parentPath; + private String currentPath = ROOT; + + + private String[] formatFilter = null; + + private File selectedFile; + private HashMap lastPositions = new HashMap(); + private String mStartPath; + private CheckBox mInlineImport; + private Button mClearButton; + private boolean mHideImport=false; + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.file_dialog_main, container,false); + + myPath = (TextView) v.findViewById(R.id.path); + + mInlineImport = (CheckBox) v.findViewById(R.id.doinline); + + if(mHideImport) { + mInlineImport.setVisibility(View.GONE); + mInlineImport.setChecked(false); + } + + + + selectButton = (Button) v.findViewById(R.id.fdButtonSelect); + selectButton.setEnabled(false); + selectButton.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + if (selectedFile != null) { + if(mInlineImport.isChecked()) + + ((FileSelect) getActivity()).importFile(selectedFile.getPath()); + else + ((FileSelect) getActivity()).setFile(selectedFile.getPath()); + } + } + }); + + mClearButton = (Button) v.findViewById(R.id.fdClear); + mClearButton.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + ((FileSelect) getActivity()).clearData(); + } + }); + if(!((FileSelect) getActivity()).showClear()) { + mClearButton.setVisibility(View.GONE); + mClearButton.setEnabled(false); + } + + return v; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + mStartPath = ((FileSelect) getActivity()).getSelectPath(); + getDir(mStartPath); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + private void getDir(String dirPath) { + + boolean useAutoSelection = dirPath.length() < currentPath.length(); + + Integer position = lastPositions.get(parentPath); + + getDirImpl(dirPath); + + if (position != null && useAutoSelection) { + getListView().setSelection(position); + } + + } + + /** + * Monta a estrutura de arquivos e diretorios filhos do diretorio fornecido. + * + * @param dirPath + * Diretorio pai. + */ + private void getDirImpl(final String dirPath) { + + currentPath = dirPath; + + final List item = new ArrayList(); + path = new ArrayList(); + mList = new ArrayList>(); + + File f = new File(currentPath); + File[] files = f.listFiles(); + if (files == null) { + currentPath = ROOT; + f = new File(currentPath); + files = f.listFiles(); + } + + myPath.setText(getText(R.string.location) + ": " + currentPath); + + if (!currentPath.equals(ROOT)) { + + item.add(ROOT); + addItem(ROOT, R.drawable.ic_root_folder_am); + path.add(ROOT); + + item.add("../"); + addItem("../", R.drawable.ic_root_folder_am); + path.add(f.getParent()); + parentPath = f.getParent(); + + } + + TreeMap dirsMap = new TreeMap(); + TreeMap dirsPathMap = new TreeMap(); + TreeMap filesMap = new TreeMap(); + TreeMap filesPathMap = new TreeMap(); + for (File file : files) { + if (file.isDirectory()) { + String dirName = file.getName(); + dirsMap.put(dirName, dirName); + dirsPathMap.put(dirName, file.getPath()); + } else { + final String fileName = file.getName(); + final String fileNameLwr = fileName.toLowerCase(Locale.getDefault()); + // se ha um filtro de formatos, utiliza-o + if (formatFilter != null) { + boolean contains = false; + for (String aFormatFilter : formatFilter) { + final String formatLwr = aFormatFilter.toLowerCase(Locale.getDefault()); + if (fileNameLwr.endsWith(formatLwr)) { + contains = true; + break; + } + } + if (contains) { + filesMap.put(fileName, fileName); + filesPathMap.put(fileName, file.getPath()); + } + // senao, adiciona todos os arquivos + } else { + filesMap.put(fileName, fileName); + filesPathMap.put(fileName, file.getPath()); + } + } + } + item.addAll(dirsMap.tailMap("").values()); + item.addAll(filesMap.tailMap("").values()); + path.addAll(dirsPathMap.tailMap("").values()); + path.addAll(filesPathMap.tailMap("").values()); + + SimpleAdapter fileList = new SimpleAdapter(getActivity(), mList, R.layout.file_dialog_row, new String[] { + ITEM_KEY, ITEM_IMAGE }, new int[] { R.id.fdrowtext, R.id.fdrowimage }); + + for (String dir : dirsMap.tailMap("").values()) { + addItem(dir, R.drawable.ic_root_folder_am); + } + + for (String file : filesMap.tailMap("").values()) { + addItem(file, R.drawable.ic_doc_generic_am); + } + + fileList.notifyDataSetChanged(); + + setListAdapter(fileList); + + } + + private void addItem(String fileName, int imageId) { + HashMap item = new HashMap(); + item.put(ITEM_KEY, fileName); + item.put(ITEM_IMAGE, imageId); + mList.add(item); + } + + + @Override + public void onListItemClick(ListView l, View v, int position, long id) { + + File file = new File(path.get(position)); + + if (file.isDirectory()) { + selectButton.setEnabled(false); + + if (file.canRead()) { + lastPositions.put(currentPath, position); + getDir(path.get(position)); + } else { + new AlertDialog.Builder(getActivity()).setIcon(R.drawable.icon) + .setTitle("[" + file.getName() + "] " + getText(R.string.cant_read_folder)) + .setPositiveButton("OK", null).show(); + } + } else { + selectedFile = file; + v.setSelected(true); + selectButton.setEnabled(true); + } + } + + public void setNoInLine() { + mHideImport=true; + + } + +} diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/InlineFileTab.java b/app/src/main/java/de/blinkt/openvpn/fragments/InlineFileTab.java new file mode 100644 index 00000000..bea22442 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/fragments/InlineFileTab.java @@ -0,0 +1,66 @@ +package de.blinkt.openvpn.fragments; + +import android.app.Fragment; +import android.os.Bundle; +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.EditText; +import de.blinkt.openvpn.activities.FileSelect; +import de.blinkt.openvpn.R; + +public class InlineFileTab extends Fragment +{ + + private static final int MENU_SAVE = 0; + private EditText mInlineData; + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + mInlineData.setText(((FileSelect)getActivity()).getInlineData()); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) + { + + View v = inflater.inflate(R.layout.file_dialog_inline, container, false); + mInlineData =(EditText) v.findViewById(R.id.inlineFileData); + return v; + } + + public void setData(String data) { + if(mInlineData!=null) + mInlineData.setText(data); + + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + menu.add(0, MENU_SAVE, 0, "Use inline data") + .setIcon(android.R.drawable.ic_menu_save) + .setAlphabeticShortcut('u') + .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM + | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if(item.getItemId()==MENU_SAVE){ + ((FileSelect)getActivity()).saveInlineData(null, mInlineData.getText().toString()); + return true; + } + return super.onOptionsItemSelected(item); + } + +} \ No newline at end of file diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java b/app/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java new file mode 100644 index 00000000..386e3133 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java @@ -0,0 +1,670 @@ +package de.blinkt.openvpn.fragments; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.app.*; +import android.content.*; +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.widget.AdapterView.OnItemLongClickListener; +import de.blinkt.openvpn.*; +import de.blinkt.openvpn.activities.DisconnectVPN; +import de.blinkt.openvpn.activities.MainActivity; +import de.blinkt.openvpn.activities.VPNPreferences; +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 org.jetbrains.annotations.Nullable; + +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Date; +import java.util.Locale; +import java.util.Vector; + +import static de.blinkt.openvpn.core.OpenVpnService.humanReadableByteCount; + +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; + private TextView mUpStatus; + private TextView mDownStatus; + private TextView mConnectStatus; + private boolean mShowOptionsLayout; + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + ladapter.setLogLevel(progress+1); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onCheckedChanged(RadioGroup group, int checkedId) { + switch (checkedId) { + case R.id.radioISO: + ladapter.setTimeFormat(LogWindowListAdapter.TIME_FORMAT_ISO); + break; + case R.id.radioNone: + ladapter.setTimeFormat(LogWindowListAdapter.TIME_FORMAT_NONE); + break; + case R.id.radioShort: + ladapter.setTimeFormat(LogWindowListAdapter.TIME_FORMAT_SHORT); + break; + + } + } + + @Override + public void updateByteCount(long in, long out, long diffIn, long diffOut) { + //%2$s/s %1$s - ↑%4$s/s %3$s + final String down = String.format("%2$s/s %1$s", humanReadableByteCount(in, false), humanReadableByteCount(diffIn / OpenVPNManagement.mBytecountInterval, true)); + final String up = String.format("%2$s/s %1$s", humanReadableByteCount(out, false), humanReadableByteCount(diffOut / OpenVPNManagement.mBytecountInterval, true)); + + if (mUpStatus != null && mDownStatus != null) { + if (getActivity() != null) { + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + mUpStatus.setText(up); + mDownStatus.setText(down); + } + }); + } + } + + } + + + class LogWindowListAdapter implements ListAdapter, LogListener, Callback { + + private static final int MESSAGE_NEWLOG = 0; + + private static final int MESSAGE_CLEARLOG = 1; + + private static final int MESSAGE_NEWTS = 2; + private static final int MESSAGE_NEWLOGLEVEL = 3; + + public static final int TIME_FORMAT_NONE = 0; + public static final int TIME_FORMAT_SHORT = 1; + public static final int TIME_FORMAT_ISO = 2; + private static final int MAX_STORED_LOG_ENTRIES = 1000; + + private Vector allEntries=new Vector(); + + private Vector currentLevelEntries=new Vector(); + + private Handler mHandler; + + private Vector observers=new Vector(); + + private int mTimeFormat=0; + private int mLogLevel=3; + + + public LogWindowListAdapter() { + initLogBuffer(); + if (mHandler == null) { + mHandler = new Handler(this); + } + + VpnStatus.addLogListener(this); + } + + + + private void initLogBuffer() { + allEntries.clear(); + Collections.addAll(allEntries, VpnStatus.getlogbuffer()); + initCurrentMessages(); + } + + String getLogStr() { + String str = ""; + for(LogItem entry:allEntries) { + str+=getTime(entry, TIME_FORMAT_ISO) + entry.getString(getActivity()) + '\n'; + } + return str; + } + + + private void shareLog() { + Intent shareIntent = new Intent(Intent.ACTION_SEND); + shareIntent.putExtra(Intent.EXTRA_TEXT, getLogStr()); + shareIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.ics_openvpn_log_file)); + shareIntent.setType("text/plain"); + startActivity(Intent.createChooser(shareIntent, "Send Logfile")); + } + + @Override + public void registerDataSetObserver(DataSetObserver observer) { + observers.add(observer); + + } + + @Override + public void unregisterDataSetObserver(DataSetObserver observer) { + observers.remove(observer); + } + + @Override + public int getCount() { + return currentLevelEntries.size(); + } + + @Override + public Object getItem(int position) { + return currentLevelEntries.get(position); + } + + @Override + public long getItemId(int position) { + return ((Object)currentLevelEntries.get(position)).hashCode(); + } + + @Override + public boolean hasStableIds() { + return true; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + TextView v; + if(convertView==null) + v = new TextView(getActivity()); + else + v = (TextView) convertView; + + LogItem le = currentLevelEntries.get(position); + String msg = le.getString(getActivity()); + String time = getTime(le, mTimeFormat); + msg = time + msg; + + int spanStart = time.length(); + + SpannableString t = new SpannableString(msg); + + //t.setSpan(getSpanImage(le,(int)v.getTextSize()),spanStart,spanStart+1, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + v.setText(t); + return v; + } + + private String getTime(LogItem le, int time) { + if (time != TIME_FORMAT_NONE) { + Date d = new Date(le.getLogtime()); + java.text.DateFormat timeformat; + if (time== TIME_FORMAT_ISO) + timeformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); + else + timeformat = DateFormat.getTimeFormat(getActivity()); + + return timeformat.format(d) + " "; + + } else { + return ""; + } + + } + + private ImageSpan getSpanImage(LogItem li, int imageSize) { + int imageRes = android.R.drawable.ic_menu_call; + + switch (li.getLogLevel()) { + case ERROR: + imageRes = android.R.drawable.ic_notification_clear_all; + break; + case INFO: + imageRes = android.R.drawable.ic_menu_compass; + break; + case VERBOSE: + imageRes = android.R.drawable.ic_menu_info_details; + break; + case WARNING: + imageRes = android.R.drawable.ic_menu_camera; + break; + } + + Drawable d = getResources().getDrawable(imageRes); + + + //d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); + d.setBounds(0, 0, imageSize, imageSize); + ImageSpan span = new ImageSpan(d, ImageSpan.ALIGN_BOTTOM); + + return span; + } + + @Override + public int getItemViewType(int position) { + return 0; + } + + @Override + public int getViewTypeCount() { + return 1; + } + + @Override + public boolean isEmpty() { + return currentLevelEntries.isEmpty(); + + } + + @Override + public boolean areAllItemsEnabled() { + return true; + } + + @Override + public boolean isEnabled(int position) { + return true; + } + + @Override + public void newLog(LogItem logMessage) { + Message msg = Message.obtain(); + assert (msg!=null); + msg.what=MESSAGE_NEWLOG; + Bundle bundle=new Bundle(); + bundle.putParcelable("logmessage", logMessage); + msg.setData(bundle); + mHandler.sendMessage(msg); + } + + @Override + public boolean handleMessage(Message msg) { + // We have been called + if(msg.what==MESSAGE_NEWLOG) { + + LogItem logMessage = msg.getData().getParcelable("logmessage"); + if(addLogMessage(logMessage)) + for (DataSetObserver observer : observers) { + observer.onChanged(); + } + } else if (msg.what == MESSAGE_CLEARLOG) { + for (DataSetObserver observer : observers) { + observer.onInvalidated(); + } + initLogBuffer(); + } else if (msg.what == MESSAGE_NEWTS) { + for (DataSetObserver observer : observers) { + observer.onInvalidated(); + } + } else if (msg.what == MESSAGE_NEWLOGLEVEL) { + initCurrentMessages(); + + for (DataSetObserver observer: observers) { + observer.onChanged(); + } + + } + + return true; + } + + private void initCurrentMessages() { + currentLevelEntries.clear(); + for(LogItem li: allEntries) { + if (li.getVerbosityLevel() <= mLogLevel || + mLogLevel == VpnProfile.MAXLOGLEVEL) + currentLevelEntries.add(li); + } + } + + /** + * + * @param logmessage + * @return True if the current entries have changed + */ + private boolean addLogMessage(LogItem logmessage) { + allEntries.add(logmessage); + + if (allEntries.size() > MAX_STORED_LOG_ENTRIES) { + Vector oldAllEntries = allEntries; + allEntries = new Vector(allEntries.size()); + for (int i=50;i parent, View view, + int position, long id) { + ClipboardManager clipboard = (ClipboardManager) + getActivity().getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText("Log Entry",((TextView) view).getText()); + clipboard.setPrimaryClip(clip); + Toast.makeText(getActivity(), R.string.copied_entry, Toast.LENGTH_SHORT).show(); + return true; + } + }); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.log_fragment, container, false); + + setHasOptionsMenu(true); + + ladapter = new LogWindowListAdapter(); + ladapter.mTimeFormat = getActivity().getPreferences(0).getInt(LOGTIMEFORMAT, 0); + int logLevel = getActivity().getPreferences(0).getInt(VERBOSITYLEVEL, 0); + ladapter.setLogLevel(logLevel); + + setListAdapter(ladapter); + + mTimeRadioGroup = (RadioGroup) v.findViewById(R.id.timeFormatRadioGroup); + mTimeRadioGroup.setOnCheckedChangeListener(this); + + if(ladapter.mTimeFormat== LogWindowListAdapter.TIME_FORMAT_ISO) { + mTimeRadioGroup.check(R.id.radioISO); + } else if (ladapter.mTimeFormat == LogWindowListAdapter.TIME_FORMAT_NONE) { + mTimeRadioGroup.check(R.id.radioNone); + } else if (ladapter.mTimeFormat == LogWindowListAdapter.TIME_FORMAT_SHORT) { + mTimeRadioGroup.check(R.id.radioShort); + } + + mSpeedView = (TextView) v.findViewById(R.id.speed); + + mOptionsLayout = (LinearLayout) v.findViewById(R.id.logOptionsLayout); + mLogLevelSlider = (SeekBar) v.findViewById(R.id.LogLevelSlider); + mLogLevelSlider.setMax(VpnProfile.MAXLOGLEVEL-1); + mLogLevelSlider.setProgress(logLevel-1); + + mLogLevelSlider.setOnSeekBarChangeListener(this); + + if(getResources().getBoolean(R.bool.logSildersAlwaysVisible)) + mOptionsLayout.setVisibility(View.VISIBLE); + + mUpStatus = (TextView) v.findViewById(R.id.speedUp); + mDownStatus = (TextView) v.findViewById(R.id.speedDown); + mConnectStatus = (TextView) v.findViewById(R.id.speedStatus); + if (mShowOptionsLayout) + mOptionsLayout.setVisibility(View.VISIBLE); + return v; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + if(getResources().getBoolean(R.bool.logSildersAlwaysVisible)) { + mShowOptionsLayout=true; + if (mOptionsLayout!= null) + mOptionsLayout.setVisibility(View.VISIBLE); + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + //getActionBar().setDisplayHomeAsUpEnabled(true); + + } + + + @Override + public void updateState(final String status, final String logMessage, final int resId, final ConnectionStatus level) { + if (isAdded()) { + getActivity().runOnUiThread(new Runnable() { + + @Override + public void run() { + if (isAdded()) { + String prefix = getString(resId) + ":"; + if (status.equals("BYTECOUNT") || status.equals("NOPROCESS")) + prefix = ""; + if (resId == R.string.unknown_state) + prefix += status; + if (mSpeedView != null) + mSpeedView.setText(prefix + logMessage); + + if (mConnectStatus != null) + mConnectStatus.setText(getString(resId)); + } + } + }); + } + } + + + @Override + public void onDestroy() { + VpnStatus.removeLogListener(ladapter); + super.onDestroy(); + } + +} diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/OpenVpnPreferencesFragment.java b/app/src/main/java/de/blinkt/openvpn/fragments/OpenVpnPreferencesFragment.java new file mode 100644 index 00000000..f23a50db --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/fragments/OpenVpnPreferencesFragment.java @@ -0,0 +1,48 @@ +package de.blinkt.openvpn.fragments; + +import android.os.Bundle; +import android.preference.PreferenceFragment; +import de.blinkt.openvpn.R; +import de.blinkt.openvpn.VpnProfile; +import de.blinkt.openvpn.core.ProfileManager; + +public abstract class OpenVpnPreferencesFragment extends PreferenceFragment { + + protected VpnProfile mProfile; + + protected abstract void loadSettings(); + protected abstract void saveSettings(); + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + String profileUUID = getArguments().getString(getActivity().getPackageName() + ".profileUUID"); + mProfile = ProfileManager.get(getActivity(),profileUUID); + getActivity().setTitle(getString(R.string.edit_profile_title, mProfile.getName())); + + } + + @Override + public void onPause() { + super.onPause(); + saveSettings(); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + if(savedInstanceState!=null) { + String profileUUID=savedInstanceState.getString(VpnProfile.EXTRA_PROFILEUUID); + mProfile = ProfileManager.get(getActivity(),profileUUID); + loadSettings(); + } + } + + @Override + public void onSaveInstanceState (Bundle outState) { + super.onSaveInstanceState(outState); + saveSettings(); + outState.putString(VpnProfile.EXTRA_PROFILEUUID, mProfile.getUUIDString()); + } +} diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/SendDumpFragment.java b/app/src/main/java/de/blinkt/openvpn/fragments/SendDumpFragment.java new file mode 100644 index 00000000..d43f6427 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/fragments/SendDumpFragment.java @@ -0,0 +1,94 @@ +package de.blinkt.openvpn.fragments; + +import java.io.File; +import java.util.ArrayList; + +import android.app.Fragment; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.net.Uri; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import de.blinkt.openvpn.R; +import de.blinkt.openvpn.core.VpnStatus; + +public class SendDumpFragment extends Fragment { + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + View v = inflater.inflate(R.layout.fragment_senddump, container, false); + v.findViewById(R.id.senddump).setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + emailMiniDumps(); + } + }); + return v; + } + + public void emailMiniDumps() + { + //need to "send multiple" to get more than one attachment + final Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND_MULTIPLE); + emailIntent.setType("*/*"); + emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL, + new String[]{"Arne Schwabe "}); + + String version; + String name="ics-openvpn"; + try { + PackageInfo packageinfo = getActivity().getPackageManager().getPackageInfo(getActivity().getPackageName(), 0); + version = packageinfo.versionName; + name = packageinfo.applicationInfo.name; + } catch (NameNotFoundException e) { + version = "error fetching version"; + } + + + emailIntent.putExtra(Intent.EXTRA_SUBJECT, String.format("%s %s Minidump",name,version)); + + emailIntent.putExtra(Intent.EXTRA_TEXT, "Please describe the issue you have experienced"); + + ArrayList uris = new ArrayList(); + + File ldump = getLastestDump(getActivity()); + if(ldump==null) { + VpnStatus.logError("No Minidump found!"); + } + + uris.add(Uri.parse("content://de.blinkt.openvpn.FileProvider/" + ldump.getName())); + uris.add(Uri.parse("content://de.blinkt.openvpn.FileProvider/" + ldump.getName() + ".log")); + + emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + emailIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); + startActivity(emailIntent); + } + + static public File getLastestDump(Context c) { + long newestDumpTime=0; + File newestDumpFile=null; + + for(File f:c.getCacheDir().listFiles()) { + if(!f.getName().endsWith(".dmp")) + continue; + + if (newestDumpTime < f.lastModified()) { + newestDumpTime = f.lastModified(); + newestDumpFile=f; + } + } + // Ignore old dumps + //if(System.currentTimeMillis() - 48 * 60 * 1000 > newestDumpTime ) + //return null; + + return newestDumpFile; + } +} diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Authentication.java b/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Authentication.java new file mode 100644 index 00000000..6ce9c915 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Authentication.java @@ -0,0 +1,214 @@ +package de.blinkt.openvpn.fragments; + +import android.app.Activity; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.EditTextPreference; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceChangeListener; +import android.preference.Preference.OnPreferenceClickListener; +import android.preference.SwitchPreference; +import android.util.Pair; +import de.blinkt.openvpn.activities.FileSelect; +import de.blinkt.openvpn.R; +import de.blinkt.openvpn.core.VpnStatus; +import de.blinkt.openvpn.views.RemoteCNPreference; +import de.blinkt.openvpn.VpnProfile; + +import java.io.IOException; + + +public class Settings_Authentication extends OpenVpnPreferencesFragment implements OnPreferenceChangeListener, OnPreferenceClickListener { + private static final int SELECT_TLS_FILE = 23223232; + private static final int SELECT_TLS_FILE_KITKAT = SELECT_TLS_FILE +1; + private CheckBoxPreference mExpectTLSCert; + private CheckBoxPreference mCheckRemoteCN; + private RemoteCNPreference mRemoteCN; + private ListPreference mTLSAuthDirection; + private Preference mTLSAuthFile; + private SwitchPreference mUseTLSAuth; + private EditTextPreference mCipher; + private String mTlsAuthFileData; + private EditTextPreference mAuth; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Load the preferences from an XML resource + addPreferencesFromResource(R.xml.vpn_authentification); + + mExpectTLSCert = (CheckBoxPreference) findPreference("remoteServerTLS"); + mCheckRemoteCN = (CheckBoxPreference) findPreference("checkRemoteCN"); + mRemoteCN = (RemoteCNPreference) findPreference("remotecn"); + mRemoteCN.setOnPreferenceChangeListener(this); + + mUseTLSAuth = (SwitchPreference) findPreference("useTLSAuth" ); + mTLSAuthFile = findPreference("tlsAuthFile"); + mTLSAuthDirection = (ListPreference) findPreference("tls_direction"); + + + mTLSAuthFile.setOnPreferenceClickListener(this); + + mCipher =(EditTextPreference) findPreference("cipher"); + mCipher.setOnPreferenceChangeListener(this); + + mAuth =(EditTextPreference) findPreference("auth"); + mAuth.setOnPreferenceChangeListener(this); + + loadSettings(); + + } + + @Override + protected void loadSettings() { + + mExpectTLSCert.setChecked(mProfile.mExpectTLSCert); + mCheckRemoteCN.setChecked(mProfile.mCheckRemoteCN); + mRemoteCN.setDN(mProfile.mRemoteCN); + mRemoteCN.setAuthType(mProfile.mX509AuthType); + onPreferenceChange(mRemoteCN, + new Pair(mProfile.mX509AuthType, mProfile.mRemoteCN)); + + mUseTLSAuth.setChecked(mProfile.mUseTLSAuth); + mTlsAuthFileData= mProfile.mTLSAuthFilename; + setTlsAuthSummary(mTlsAuthFileData); + mTLSAuthDirection.setValue(mProfile.mTLSAuthDirection); + mCipher.setText(mProfile.mCipher); + onPreferenceChange(mCipher, mProfile.mCipher); + mAuth.setText(mProfile.mAuth); + onPreferenceChange(mAuth, mProfile.mAuth); + + if (mProfile.mAuthenticationType == VpnProfile.TYPE_STATICKEYS) { + mExpectTLSCert.setEnabled(false); + mCheckRemoteCN.setEnabled(false); + mUseTLSAuth.setChecked(true); + } else { + mExpectTLSCert.setEnabled(true); + mCheckRemoteCN.setEnabled(true); + + } + } + + @Override + protected void saveSettings() { + mProfile.mExpectTLSCert=mExpectTLSCert.isChecked(); + mProfile.mCheckRemoteCN=mCheckRemoteCN.isChecked(); + mProfile.mRemoteCN=mRemoteCN.getCNText(); + mProfile.mX509AuthType=mRemoteCN.getAuthtype(); + + mProfile.mUseTLSAuth = mUseTLSAuth.isChecked(); + mProfile.mTLSAuthFilename = mTlsAuthFileData; + + if(mTLSAuthDirection.getValue()==null) + mProfile.mTLSAuthDirection=null; + else + mProfile.mTLSAuthDirection = mTLSAuthDirection.getValue(); + + if(mCipher.getText()==null) + mProfile.mCipher=null; + else + mProfile.mCipher = mCipher.getText(); + + if(mAuth.getText()==null) + mProfile.mAuth = null; + else + mProfile.mAuth = mAuth.getText(); + + } + + + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if(preference==mRemoteCN) { + @SuppressWarnings("unchecked") + int authtype = ((Pair) newValue).first; + @SuppressWarnings("unchecked") + String dn = ((Pair) newValue).second; + + if ("".equals(dn)) + preference.setSummary(getX509String(VpnProfile.X509_VERIFY_TLSREMOTE_RDN, mProfile.mServerName)); + else + preference.setSummary(getX509String(authtype,dn)); + + } else if (preference == mCipher || preference == mAuth) { + preference.setSummary((CharSequence) newValue); + } + return true; + } + private CharSequence getX509String(int authtype, String dn) { + String ret =""; + switch (authtype) { + case VpnProfile.X509_VERIFY_TLSREMOTE: + case VpnProfile.X509_VERIFY_TLSREMOTE_COMPAT_NOREMAPPING: + ret+="tls-remote "; + break; + + case VpnProfile.X509_VERIFY_TLSREMOTE_DN: + ret="dn: "; + break; + + case VpnProfile.X509_VERIFY_TLSREMOTE_RDN: + ret="rdn: "; + break; + + case VpnProfile.X509_VERIFY_TLSREMOTE_RDN_PREFIX: + ret="rdn prefix: "; + break; + } + return ret + dn; + } + + void startFileDialog() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + Intent startFC = Utils.getFilePickerIntent (getActivity(), Utils.FileType.TLS_AUTH_FILE); + startActivityForResult(startFC, SELECT_TLS_FILE_KITKAT); + } else { + Intent startFC = new Intent(getActivity(), FileSelect.class); + startFC.putExtra(FileSelect.START_DATA, mTlsAuthFileData); + startFC.putExtra(FileSelect.WINDOW_TITLE, R.string.tls_auth_file); + startActivityForResult(startFC, SELECT_TLS_FILE); + } + } + + @Override + public boolean onPreferenceClick(Preference preference) { + startFileDialog(); + return true; + + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if(requestCode==SELECT_TLS_FILE && resultCode == Activity.RESULT_OK){ + String result = data.getStringExtra(FileSelect.RESULT_DATA); + mTlsAuthFileData=result; + setTlsAuthSummary(result); + } else if (requestCode == SELECT_TLS_FILE_KITKAT && resultCode == Activity.RESULT_OK) { + try { + mTlsAuthFileData= Utils.getFilePickerResult(Utils.FileType.TLS_AUTH_FILE,data,getActivity()); + setTlsAuthSummary(mTlsAuthFileData); + } catch (IOException e) { + VpnStatus.logException(e); + } catch (SecurityException se) { + VpnStatus.logException(se); + } + } + } + + private void setTlsAuthSummary(String result) { + if(result==null) + result = getString(R.string.no_certificate); + if(result.startsWith(VpnProfile.INLINE_TAG)) + mTLSAuthFile.setSummary(R.string.inline_file_data); + else if (result.startsWith(VpnProfile.DISPLAYNAME_TAG)) + mExpectTLSCert.setSummary(getString(R.string.imported_from_file, VpnProfile.getDisplayName(result))); + else + mTLSAuthFile.setSummary(result); + } +} \ No newline at end of file diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Basic.java b/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Basic.java new file mode 100644 index 00000000..4145c65f --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Basic.java @@ -0,0 +1,360 @@ +package de.blinkt.openvpn.fragments; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.AlertDialog.Builder; +import android.app.Fragment; +import android.content.ActivityNotFoundException; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Handler.Callback; +import android.os.Message; +import android.security.KeyChain; +import android.security.KeyChainAliasCallback; +import android.security.KeyChainException; +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.ToggleButton; +import de.blinkt.openvpn.views.FileSelectLayout; +import de.blinkt.openvpn.R; +import de.blinkt.openvpn.VpnProfile; +import de.blinkt.openvpn.R.id; +import de.blinkt.openvpn.core.ProfileManager; +import de.blinkt.openvpn.core.X509Utils; + +import java.security.cert.X509Certificate; + +public class Settings_Basic extends Fragment implements View.OnClickListener, OnItemSelectedListener, Callback, FileSelectLayout.FileSelectCallback { + private static final int CHOOSE_FILE_OFFSET = 1000; + private static final int UPDATE_ALIAS = 20; + + private TextView mServerAddress; + private TextView mServerPort; + private FileSelectLayout mClientCert; + private FileSelectLayout mCaCert; + private FileSelectLayout mClientKey; + private TextView mAliasName; + private TextView mAliasCertificate; + private CheckBox mUseLzo; + private ToggleButton mTcpUdp; + private Spinner mType; + private FileSelectLayout mpkcs12; + private TextView mPKCS12Password; + private Handler mHandler; + private EditText mUserName; + private EditText mPassword; + private View mView; + private VpnProfile mProfile; + private EditText mProfileName; + private EditText mKeyPassword; + + private SparseArray fileselects = new SparseArray(); + + + + private void addFileSelectLayout (FileSelectLayout fsl, Utils.FileType type) { + int i = fileselects.size() + CHOOSE_FILE_OFFSET; + fileselects.put(i, fsl); + fsl.setCaller(this, i, type); + } + + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + String profileUuid = getArguments().getString(getActivity().getPackageName() + ".profileUUID"); + mProfile=ProfileManager.get(getActivity(),profileUuid); + getActivity().setTitle(getString(R.string.edit_profile_title, mProfile.getName())); + } + + + private void setKeystoreCertficate() + { + new Thread() { + public void run() { + String certstr=""; + try { + X509Certificate cert = KeyChain.getCertificateChain(getActivity(), mProfile.mAlias)[0]; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + { + if (isInHardwareKeystore()) + certstr+=getString(R.string.hwkeychain); + } + } + + certstr+=X509Utils.getCertificateFriendlyName(cert); + } catch (Exception e) { + certstr="Could not get certificate from Keystore: " +e.getLocalizedMessage(); + } + + final String certStringCopy=certstr; + getActivity().runOnUiThread(new Runnable() { + + @Override + public void run() { + mAliasCertificate.setText(certStringCopy); + } + }); + + } + }.start(); + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) + private boolean isInHardwareKeystore() throws KeyChainException, InterruptedException { + String algorithm = KeyChain.getPrivateKey(getActivity(), mProfile.mAlias).getAlgorithm(); + return KeyChain.isBoundKeyAlgorithm(algorithm); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + + + mView = inflater.inflate(R.layout.basic_settings,container,false); + + mProfileName = (EditText) mView.findViewById(R.id.profilename); + mServerAddress = (TextView) mView.findViewById(R.id.address); + mServerPort = (TextView) mView.findViewById(R.id.port); + mClientCert = (FileSelectLayout) mView.findViewById(R.id.certselect); + mClientKey = (FileSelectLayout) mView.findViewById(R.id.keyselect); + mCaCert = (FileSelectLayout) mView.findViewById(R.id.caselect); + mpkcs12 = (FileSelectLayout) mView.findViewById(R.id.pkcs12select); + mUseLzo = (CheckBox) mView.findViewById(R.id.lzo); + mTcpUdp = (ToggleButton) mView.findViewById(id.tcpudp); + mType = (Spinner) mView.findViewById(R.id.type); + mPKCS12Password = (TextView) mView.findViewById(R.id.pkcs12password); + mAliasName = (TextView) mView.findViewById(R.id.aliasname); + mAliasCertificate = (TextView) mView.findViewById(id.alias_certificate); + + mUserName = (EditText) mView.findViewById(R.id.auth_username); + mPassword = (EditText) mView.findViewById(R.id.auth_password); + mKeyPassword = (EditText) mView.findViewById(R.id.key_password); + + addFileSelectLayout(mCaCert, Utils.FileType.CA_CERTIFICATE); + addFileSelectLayout(mClientCert, Utils.FileType.CLIENT_CERTIFICATE); + addFileSelectLayout(mClientKey, Utils.FileType.KEYFILE); + addFileSelectLayout(mpkcs12, Utils.FileType.PKCS12); + mCaCert.setShowClear(); + + mType.setOnItemSelectedListener(this); + + mView.findViewById(R.id.select_keystore_button).setOnClickListener(this); + + + if (mHandler == null) { + mHandler = new Handler(this); + } + + return mView; + } + + + @Override + public void onStart() { + super.onStart(); + String profileUuid =getArguments().getString(getActivity().getPackageName() + ".profileUUID"); + mProfile=ProfileManager.get(getActivity(),profileUuid); + loadPreferences(); + + } + + @Override + public void onActivityResult(int request, int result, Intent data) { + if (result == Activity.RESULT_OK && request >= CHOOSE_FILE_OFFSET) { + + FileSelectLayout fsl = fileselects.get(request); + fsl.parseResponse(data, getActivity()); + + savePreferences(); + + // Private key files may result in showing/hiding the private key password dialog + if(fsl==mClientKey) { + changeType(mType.getSelectedItemPosition()); + } + } + + } + + + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + if (parent == mType) { + changeType(position); + } + } + @Override + public void onPause() { + super.onPause(); + savePreferences(); + } + + + + private void changeType(int type){ + // hide everything + mView.findViewById(R.id.pkcs12).setVisibility(View.GONE); + mView.findViewById(R.id.certs).setVisibility(View.GONE); + mView.findViewById(R.id.statickeys).setVisibility(View.GONE); + mView.findViewById(R.id.keystore).setVisibility(View.GONE); + mView.findViewById(R.id.cacert).setVisibility(View.GONE); + mView.findViewById(R.id.userpassword).setVisibility(View.GONE); + mView.findViewById(R.id.key_password_layout).setVisibility(View.GONE); + + // Fall through are by design + switch(type) { + case VpnProfile.TYPE_USERPASS_CERTIFICATES: + mView.findViewById(R.id.userpassword).setVisibility(View.VISIBLE); + case VpnProfile.TYPE_CERTIFICATES: + mView.findViewById(R.id.certs).setVisibility(View.VISIBLE); + mView.findViewById(R.id.cacert).setVisibility(View.VISIBLE); + if(mProfile.requireTLSKeyPassword()) + mView.findViewById(R.id.key_password_layout).setVisibility(View.VISIBLE); + break; + + case VpnProfile.TYPE_USERPASS_PKCS12: + mView.findViewById(R.id.userpassword).setVisibility(View.VISIBLE); + case VpnProfile.TYPE_PKCS12: + mView.findViewById(R.id.pkcs12).setVisibility(View.VISIBLE); + break; + + case VpnProfile.TYPE_STATICKEYS: + mView.findViewById(R.id.statickeys).setVisibility(View.VISIBLE); + break; + + case VpnProfile.TYPE_USERPASS_KEYSTORE: + mView.findViewById(R.id.userpassword).setVisibility(View.VISIBLE); + case VpnProfile.TYPE_KEYSTORE: + mView.findViewById(R.id.keystore).setVisibility(View.VISIBLE); + mView.findViewById(R.id.cacert).setVisibility(View.VISIBLE); + break; + + case VpnProfile.TYPE_USERPASS: + mView.findViewById(R.id.userpassword).setVisibility(View.VISIBLE); + mView.findViewById(R.id.cacert).setVisibility(View.VISIBLE); + break; + } + + + } + + private void loadPreferences() { + mProfileName.setText(mProfile.mName); + mClientCert.setData(mProfile.mClientCertFilename, getActivity()); + mClientKey.setData(mProfile.mClientKeyFilename, getActivity()); + mCaCert.setData(mProfile.mCaFilename, getActivity()); + + mUseLzo.setChecked(mProfile.mUseLzo); + mServerPort.setText(mProfile.mServerPort); + mServerAddress.setText(mProfile.mServerName); + mTcpUdp.setChecked(mProfile.mUseUdp); + mType.setSelection(mProfile.mAuthenticationType); + mpkcs12.setData(mProfile.mPKCS12Filename, getActivity()); + mPKCS12Password.setText(mProfile.mPKCS12Password); + mUserName.setText(mProfile.mUsername); + mPassword.setText(mProfile.mPassword); + mKeyPassword.setText(mProfile.mKeyPassword); + + setAlias(); + + } + + void savePreferences() { + + mProfile.mName = mProfileName.getText().toString(); + mProfile.mCaFilename = mCaCert.getData(); + mProfile.mClientCertFilename = mClientCert.getData(); + mProfile.mClientKeyFilename = mClientKey.getData(); + + mProfile.mUseLzo = mUseLzo.isChecked(); + mProfile.mServerPort =mServerPort.getText().toString(); + mProfile.mServerName = mServerAddress.getText().toString(); + mProfile.mUseUdp = mTcpUdp.isChecked(); + + mProfile.mAuthenticationType = mType.getSelectedItemPosition(); + mProfile.mPKCS12Filename = mpkcs12.getData(); + mProfile.mPKCS12Password = mPKCS12Password.getText().toString(); + + mProfile.mPassword = mPassword.getText().toString(); + mProfile.mUsername = mUserName.getText().toString(); + mProfile.mKeyPassword = mKeyPassword.getText().toString(); + + } + + + private void setAlias() { + if(mProfile.mAlias == null) { + mAliasName.setText(R.string.client_no_certificate); + mAliasCertificate.setText(""); + } else { + mAliasCertificate.setText("Loading certificate from Keystore..."); + mAliasName.setText(mProfile.mAlias); + setKeystoreCertficate(); + } + } + + public void showCertDialog () { + try { + KeyChain.choosePrivateKeyAlias(getActivity(), + new KeyChainAliasCallback() { + + public void alias(String alias) { + // Credential alias selected. Remember the alias selection for future use. + mProfile.mAlias=alias; + mHandler.sendEmptyMessage(UPDATE_ALIAS); + } + + + }, + new String[] {"RSA"}, // List of acceptable key types. null for any + null, // issuer, null for any + mProfile.mServerName, // host name of server requesting the cert, null if unavailable + -1, // port of server requesting the cert, -1 if unavailable + mProfile.mAlias); // alias to preselect, null if unavailable + } catch (ActivityNotFoundException anf) { + Builder ab = new AlertDialog.Builder(getActivity()); + ab.setTitle(R.string.broken_image_cert_title); + ab.setMessage(R.string.broken_image_cert); + ab.setPositiveButton(android.R.string.ok, null); + ab.show(); + } + } + + @Override + public void onClick(View v) { + if (v == mView.findViewById(R.id.select_keystore_button)) { + showCertDialog(); + } + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + savePreferences(); + if(mProfile!=null) { + outState.putString(getActivity().getPackageName() + "profileUUID", mProfile.getUUID().toString()); + } + } + + @Override + public void onNothingSelected(AdapterView parent) { + } + + + @Override + public boolean handleMessage(Message msg) { + setAlias(); + return true; + } + + +} diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/Settings_IP.java b/app/src/main/java/de/blinkt/openvpn/fragments/Settings_IP.java new file mode 100644 index 00000000..16e3a5c4 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/fragments/Settings_IP.java @@ -0,0 +1,130 @@ +package de.blinkt.openvpn.fragments; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.EditTextPreference; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceChangeListener; +import android.preference.PreferenceManager; +import android.preference.SwitchPreference; +import de.blinkt.openvpn.R; +import de.blinkt.openvpn.VpnProfile; + +public class Settings_IP extends OpenVpnPreferencesFragment implements OnPreferenceChangeListener { + private EditTextPreference mIPv4; + private EditTextPreference mIPv6; + private SwitchPreference mUsePull; + private CheckBoxPreference mOverrideDNS; + private EditTextPreference mSearchdomain; + private EditTextPreference mDNS1; + private EditTextPreference mDNS2; + private CheckBoxPreference mNobind; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + + // Make sure default values are applied. In a real app, you would + // want this in a shared function that is used to retrieve the + // SharedPreferences wherever they are needed. + PreferenceManager.setDefaultValues(getActivity(), + R.xml.vpn_ipsettings, false); + + // Load the preferences from an XML resource + addPreferencesFromResource(R.xml.vpn_ipsettings); + mIPv4 = (EditTextPreference) findPreference("ipv4_address"); + mIPv6 = (EditTextPreference) findPreference("ipv6_address"); + mUsePull = (SwitchPreference) findPreference("usePull"); + mOverrideDNS = (CheckBoxPreference) findPreference("overrideDNS"); + mSearchdomain =(EditTextPreference) findPreference("searchdomain"); + mDNS1 = (EditTextPreference) findPreference("dns1"); + mDNS2 = (EditTextPreference) findPreference("dns2"); + mNobind = (CheckBoxPreference) findPreference("nobind"); + + mIPv4.setOnPreferenceChangeListener(this); + mIPv6.setOnPreferenceChangeListener(this); + mDNS1.setOnPreferenceChangeListener(this); + mDNS2.setOnPreferenceChangeListener(this); + mUsePull.setOnPreferenceChangeListener(this); + mOverrideDNS.setOnPreferenceChangeListener(this); + mSearchdomain.setOnPreferenceChangeListener(this); + + loadSettings(); + } + + @Override + protected void loadSettings() { + + mUsePull.setChecked(mProfile.mUsePull); + mIPv4.setText(mProfile.mIPv4Address); + mIPv6.setText(mProfile.mIPv6Address); + mDNS1.setText(mProfile.mDNS1); + mDNS2.setText(mProfile.mDNS2); + mOverrideDNS.setChecked(mProfile.mOverrideDNS); + mSearchdomain.setText(mProfile.mSearchDomain); + mNobind.setChecked(mProfile.mNobind); + if (mProfile.mAuthenticationType == VpnProfile.TYPE_STATICKEYS) + mUsePull.setChecked(false); + + // Sets Summary + onPreferenceChange(mIPv4, mIPv4.getText()); + onPreferenceChange(mIPv6, mIPv6.getText()); + onPreferenceChange(mDNS1, mDNS1.getText()); + onPreferenceChange(mDNS2, mDNS2.getText()); + onPreferenceChange(mSearchdomain, mSearchdomain.getText()); + + setDNSState(); + } + + + @Override + protected void saveSettings() { + mProfile.mUsePull = mUsePull.isChecked(); + mProfile.mIPv4Address = mIPv4.getText(); + mProfile.mIPv6Address = mIPv6.getText(); + mProfile.mDNS1 = mDNS1.getText(); + mProfile.mDNS2 = mDNS2.getText(); + mProfile.mOverrideDNS = mOverrideDNS.isChecked(); + mProfile.mSearchDomain = mSearchdomain.getText(); + mProfile.mNobind = mNobind.isChecked(); + + } + + @Override + public boolean onPreferenceChange(Preference preference, + Object newValue) { + if(preference==mIPv4 || preference == mIPv6 + || preference==mDNS1 || preference == mDNS2 + || preference == mSearchdomain + ) + + preference.setSummary((String)newValue); + + if(preference== mUsePull || preference == mOverrideDNS) + if(preference==mOverrideDNS) { + // Set so the function gets the right value + mOverrideDNS.setChecked((Boolean) newValue); + } + setDNSState(); + + saveSettings(); + return true; + } + + private void setDNSState() { + boolean enabled; + mOverrideDNS.setEnabled(mUsePull.isChecked()); + if(!mUsePull.isChecked()) + enabled =true; + else + enabled = mOverrideDNS.isChecked(); + + mDNS1.setEnabled(enabled); + mDNS2.setEnabled(enabled); + mSearchdomain.setEnabled(enabled); + + + } + + + } \ No newline at end of file diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Obscure.java b/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Obscure.java new file mode 100644 index 00000000..0e8f1a02 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Obscure.java @@ -0,0 +1,93 @@ +package de.blinkt.openvpn.fragments; + +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.EditTextPreference; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceChangeListener; +import de.blinkt.openvpn.R; + +public class Settings_Obscure extends OpenVpnPreferencesFragment implements OnPreferenceChangeListener { + private CheckBoxPreference mUseRandomHostName; + private CheckBoxPreference mUseFloat; + private CheckBoxPreference mUseCustomConfig; + private EditTextPreference mCustomConfig; + private ListPreference mLogverbosity; + private CheckBoxPreference mPersistent; + private ListPreference mConnectretrymax; + private EditTextPreference mConnectretry; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // Load the preferences from an XML resource + addPreferencesFromResource(R.xml.vpn_obscure); + + mUseRandomHostName = (CheckBoxPreference) findPreference("useRandomHostname"); + mUseFloat = (CheckBoxPreference) findPreference("useFloat"); + mUseCustomConfig = (CheckBoxPreference) findPreference("enableCustomOptions"); + mCustomConfig = (EditTextPreference) findPreference("customOptions"); + mPersistent = (CheckBoxPreference) findPreference("usePersistTun"); + mConnectretrymax = (ListPreference) findPreference("connectretrymax"); + mConnectretry = (EditTextPreference) findPreference("connectretry"); + + mConnectretrymax.setOnPreferenceChangeListener(this); + mConnectretrymax.setSummary("%s"); + + mConnectretry.setOnPreferenceChangeListener(this); + + + loadSettings(); + + } + + protected void loadSettings() { + mUseRandomHostName.setChecked(mProfile.mUseRandomHostname); + mUseFloat.setChecked(mProfile.mUseFloat); + mUseCustomConfig.setChecked(mProfile.mUseCustomConfig); + mCustomConfig.setText(mProfile.mCustomConfigOptions); + mPersistent.setChecked(mProfile.mPersistTun); + + mConnectretrymax.setValue(mProfile.mConnectRetryMax); + onPreferenceChange(mConnectretrymax, mProfile.mConnectRetryMax); + + mConnectretry.setText(mProfile.mConnectRetry); + onPreferenceChange(mConnectretry, mProfile.mConnectRetry); + } + + + protected void saveSettings() { + mProfile.mUseRandomHostname = mUseRandomHostName.isChecked(); + mProfile.mUseFloat = mUseFloat.isChecked(); + mProfile.mUseCustomConfig = mUseCustomConfig.isChecked(); + mProfile.mCustomConfigOptions = mCustomConfig.getText(); + mProfile.mConnectRetryMax = mConnectretrymax.getValue(); + mProfile.mPersistTun = mPersistent.isChecked(); + mProfile.mConnectRetry = mConnectretry.getText(); + } + + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (preference == mConnectretrymax) { + if(newValue==null) { + newValue="5"; + } + mConnectretrymax.setDefaultValue(newValue); + + for(int i=0;i supportedMimeTypes = new TreeSet(); + Vector extensions = new Vector(); + + switch (fileType) { + case PKCS12: + i.setType("application/x-pkcs12"); + supportedMimeTypes.add("application/x-pkcs12"); + extensions.add("p12"); + extensions.add("pfx"); + break; + case CLIENT_CERTIFICATE: + case CA_CERTIFICATE: + i.setType("application/x-pem-file"); + supportedMimeTypes.add("application/x-x509-ca-cert"); + supportedMimeTypes.add("application/x-x509-user-cert"); + supportedMimeTypes.add("application/x-pem-file"); + supportedMimeTypes.add("text/plain"); + + extensions.add("pem"); + extensions.add("crt"); + break; + case KEYFILE: + i.setType("application/x-pem-file"); + supportedMimeTypes.add("application/x-pem-file"); + supportedMimeTypes.add("application/pkcs8"); + + // Google drive .... + supportedMimeTypes.add("application/x-iwork-keynote-sffkey"); + extensions.add("key"); + break; + + case TLS_AUTH_FILE: + i.setType("text/plain"); + + // Backup .... + supportedMimeTypes.add("application/pkcs8"); + // Google Drive is kind of crazy ..... + supportedMimeTypes.add("application/x-iwork-keynote-sffkey"); + + extensions.add("txt"); + extensions.add("key"); + break; + + case OVPN_CONFIG: + i.setType("application/x-openvpn-profile"); + supportedMimeTypes.add("application/x-openvpn-profile"); + supportedMimeTypes.add("application/openvpn-profile"); + supportedMimeTypes.add("application/ovpn"); + supportedMimeTypes.add("text/plain"); + extensions.add("ovpn"); + extensions.add("conf"); + break; + + case USERPW_FILE: + i.setType("text/plain"); + supportedMimeTypes.add("text/plain"); + break; + } + + MimeTypeMap mtm = MimeTypeMap.getSingleton(); + + for (String ext : extensions) { + String mimeType = mtm.getMimeTypeFromExtension(ext); + if (mimeType != null) + supportedMimeTypes.add(mimeType); + } + + // Always add this as fallback + supportedMimeTypes.add("application/octet-stream"); + + i.putExtra(Intent.EXTRA_MIME_TYPES, supportedMimeTypes.toArray(new String[supportedMimeTypes.size()])); + + + /* Samsung has decided to do something strange, on stock Android GET_CONTENT opens the document UI */ + /* fist try with documentsui */ + i.setPackage("com.android.documentsui"); + + //noinspection ConstantConditions + if (true || !isIntentAvailable(c,i)) { + i.setAction(Intent.ACTION_OPEN_DOCUMENT); + i.setPackage(null); + } + + return i; + } + + + public static boolean isIntentAvailable(Context context, Intent i) { + final PackageManager packageManager = context.getPackageManager(); + List list = + packageManager.queryIntentActivities(i, + PackageManager.MATCH_DEFAULT_ONLY); + return list.size() > 0; + } + + + public enum FileType { + PKCS12(0), + CLIENT_CERTIFICATE(1), + CA_CERTIFICATE(2), + OVPN_CONFIG(3), + KEYFILE(4), + TLS_AUTH_FILE(5), + USERPW_FILE(6); + + private int value; + + FileType(int i) { + value = i; + } + + public static FileType getFileTypeByValue(int value) { + switch (value) { + case 0: + return PKCS12; + case 1: + return CLIENT_CERTIFICATE; + case 2: + return CA_CERTIFICATE; + case 3: + return OVPN_CONFIG; + case 4: + return KEYFILE; + case 5: + return TLS_AUTH_FILE; + case 6: + return USERPW_FILE; + default: + return null; + } + } + + public int getValue() { + return value; + } + } + + static private byte[] readBytesFromStream(InputStream input) throws IOException { + + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + + int nRead; + byte[] data = new byte[16384]; + + while ((nRead = input.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, nRead); + } + + buffer.flush(); + input.close(); + return buffer.toByteArray(); + } + + public static String getFilePickerResult(FileType ft, Intent result, Context c) throws IOException, SecurityException { + + Uri uri = result.getData(); + if (uri == null) + return null; + + byte[] fileData = readBytesFromStream(c.getContentResolver().openInputStream(uri)); + String newData = null; + + Cursor cursor = c.getContentResolver().query(uri, null, null, null, null); + + String prefix = ""; + try { + if (cursor!=null && cursor.moveToFirst()) { + int cidx = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); + if (cidx != -1) { + String displayName = cursor.getString(cidx); + + if (!displayName.contains(VpnProfile.INLINE_TAG) && !displayName.contains(VpnProfile.DISPLAYNAME_TAG)) + prefix = VpnProfile.DISPLAYNAME_TAG + displayName; + } + } + } finally { + if(cursor!=null) + cursor.close(); + } + + switch (ft) { + case PKCS12: + newData = Base64.encodeToString(fileData, Base64.DEFAULT); + break; + default: + newData = new String(fileData, "UTF-8"); + break; + } + + return prefix + VpnProfile.INLINE_TAG + newData; + } +} diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/VPNProfileList.java b/app/src/main/java/de/blinkt/openvpn/fragments/VPNProfileList.java new file mode 100644 index 00000000..693a7e71 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/fragments/VPNProfileList.java @@ -0,0 +1,346 @@ +package de.blinkt.openvpn.fragments; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.ListFragment; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.text.Html; +import android.text.Html.ImageGetter; +import android.view.*; +import android.view.View.OnClickListener; +import android.webkit.MimeTypeMap; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; +import de.blinkt.openvpn.*; +import de.blinkt.openvpn.activities.ConfigConverter; +import de.blinkt.openvpn.activities.FileSelect; +import de.blinkt.openvpn.activities.VPNPreferences; +import de.blinkt.openvpn.core.ProfileManager; + +import java.util.Collection; +import java.util.Comparator; +import java.util.TreeSet; + +public class VPNProfileList extends ListFragment { + + public final static int RESULT_VPN_DELETED = Activity.RESULT_FIRST_USER; + + private static final int MENU_ADD_PROFILE = Menu.FIRST; + + private static final int START_VPN_CONFIG = 92; + private static final int SELECT_PROFILE = 43; + private static final int IMPORT_PROFILE = 231; + private static final int FILE_PICKER_RESULT = 392; + + private static final int MENU_IMPORT_PROFILE = Menu.FIRST +1; + + + class VPNArrayAdapter extends ArrayAdapter { + + public VPNArrayAdapter(Context context, int resource, + int textViewResourceId) { + super(context, resource, textViewResourceId); + } + + @Override + public View getView(final int position, View convertView, ViewGroup parent) { + View v = super.getView(position, convertView, parent); + + View titleview = v.findViewById(R.id.vpn_list_item_left); + titleview.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + VpnProfile profile =(VpnProfile) getListAdapter().getItem(position); + startVPN(profile); + } + }); + + View settingsview = v.findViewById(R.id.quickedit_settings); + settingsview.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + VpnProfile editProfile = (VpnProfile) getListAdapter().getItem(position); + editVPN(editProfile); + + } + }); + + return v; + } + } + + + + + + + + + private ArrayAdapter mArrayadapter; + + protected VpnProfile mEditProfile=null; + + + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + + } + + + class MiniImageGetter implements ImageGetter { + + + @Override + public Drawable getDrawable(String source) { + Drawable d=null; + if ("ic_menu_add".equals(source)) + d = getActivity().getResources().getDrawable(android.R.drawable.ic_menu_add); + else if("ic_menu_archive".equals(source)) + d = getActivity().getResources().getDrawable(R.drawable.ic_menu_archive); + + + + if(d!=null) { + d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); + return d; + }else{ + return null; + } + } + } + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.vpn_profile_list, container,false); + + TextView newvpntext = (TextView) v.findViewById(R.id.add_new_vpn_hint); + TextView importvpntext = (TextView) v.findViewById(R.id.import_vpn_hint); + + + + newvpntext.setText(Html.fromHtml(getString(R.string.add_new_vpn_hint),new MiniImageGetter(),null)); + importvpntext.setText(Html.fromHtml(getString(R.string.vpn_import_hint),new MiniImageGetter(),null)); + + + + return v; + + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + setListAdapter(); + } + + static class VpnProfileNameComparator implements Comparator { + + @Override + public int compare(VpnProfile lhs, VpnProfile rhs) { + if (lhs == rhs) + // Catches also both null + return 0; + + if (lhs == null) + return -1; + if (rhs == null) + return 1; + + if (lhs.mName == null) + return -1; + if (rhs.mName == null) + return 1; + + return lhs.mName.compareTo(rhs.mName); + } + + } + + private void setListAdapter() { + mArrayadapter = new VPNArrayAdapter(getActivity(),R.layout.vpn_list_item,R.id.vpn_item_title); + Collection allvpn = getPM().getProfiles(); + + TreeSet sortedset = new TreeSet(new VpnProfileNameComparator()); + sortedset.addAll(allvpn); + mArrayadapter.addAll(sortedset); + + setListAdapter(mArrayadapter); + } + + + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + menu.add(0, MENU_ADD_PROFILE, 0 , R.string.menu_add_profile) + .setIcon(android.R.drawable.ic_menu_add) + .setAlphabeticShortcut('a') + .setTitleCondensed(getActivity().getString(R.string.add)) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + + menu.add(0, MENU_IMPORT_PROFILE, 0, R.string.menu_import) + .setIcon(R.drawable.ic_menu_archive) + .setAlphabeticShortcut('i') + .setTitleCondensed(getActivity().getString(R.string.menu_import_short)) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT ); + } + + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + final int itemId = item.getItemId(); + if (itemId == MENU_ADD_PROFILE) { + onAddProfileClicked(); + return true; + } else if (itemId == MENU_IMPORT_PROFILE) { + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) + startFilePicker(); + else + startImportConfig(); + + return true; + } else { + return super.onOptionsItemSelected(item); + } + } + + @TargetApi(Build.VERSION_CODES.KITKAT) + private void startFilePicker() { + Intent i = Utils.getFilePickerIntent(getActivity(), Utils.FileType.OVPN_CONFIG); + startActivityForResult(i, FILE_PICKER_RESULT); + } + + private void startImportConfig() { + Intent intent = new Intent(getActivity(),FileSelect.class); + intent.putExtra(FileSelect.NO_INLINE_SELECTION, true); + intent.putExtra(FileSelect.WINDOW_TITLE, R.string.import_configuration_file); + startActivityForResult(intent, SELECT_PROFILE); + } + + + + + + private void onAddProfileClicked() { + Context context = getActivity(); + if (context != null) { + final EditText entry = new EditText(context); + entry.setSingleLine(); + + AlertDialog.Builder dialog = new AlertDialog.Builder(context); + dialog.setTitle(R.string.menu_add_profile); + dialog.setMessage(R.string.add_profile_name_prompt); + dialog.setView(entry); + + + dialog.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String name = entry.getText().toString(); + if (getPM().getProfileByName(name)==null) { + VpnProfile profile = new VpnProfile(name); + addProfile(profile); + editVPN(profile); + } else { + Toast.makeText(getActivity(), R.string.duplicate_profile_name, Toast.LENGTH_LONG).show(); + } + } + + + }); + dialog.setNegativeButton(android.R.string.cancel, null); + dialog.create().show(); + } + + } + + private void addProfile(VpnProfile profile) { + getPM().addProfile(profile); + getPM().saveProfileList(getActivity()); + getPM().saveProfile(getActivity(),profile); + mArrayadapter.add(profile); + } + + private ProfileManager getPM() { + return ProfileManager.getInstance(getActivity()); + } + + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if(resultCode == RESULT_VPN_DELETED){ + if(mArrayadapter != null && mEditProfile !=null) + mArrayadapter.remove(mEditProfile); + } + + if(resultCode != Activity.RESULT_OK) + return; + + if (requestCode == START_VPN_CONFIG) { + String configuredVPN = data.getStringExtra(VpnProfile.EXTRA_PROFILEUUID); + + VpnProfile profile = ProfileManager.get(getActivity(),configuredVPN); + getPM().saveProfile(getActivity(), profile); + // Name could be modified, reset List adapter + setListAdapter(); + + } else if(requestCode== SELECT_PROFILE) { + String fileData = data.getStringExtra(FileSelect.RESULT_DATA); + Uri uri = new Uri.Builder().path(fileData).scheme("file").build(); + + startConfigImport(uri); + } else if(requestCode == IMPORT_PROFILE) { + String profileUUID = data.getStringExtra(VpnProfile.EXTRA_PROFILEUUID); + mArrayadapter.add(ProfileManager.get(getActivity(), profileUUID)); + } else if(requestCode == FILE_PICKER_RESULT) { + if (data != null) { + Uri uri = data.getData(); + startConfigImport(uri); + } + } + + } + + private void startConfigImport(Uri uri) { + Intent startImport = new Intent(getActivity(),ConfigConverter.class); + startImport.setAction(ConfigConverter.IMPORT_PROFILE); + startImport.setData(uri); + startActivityForResult(startImport, IMPORT_PROFILE); + } + + + private void editVPN(VpnProfile profile) { + mEditProfile =profile; + Intent vprefintent = new Intent(getActivity(),VPNPreferences.class) + .putExtra(getActivity().getPackageName() + ".profileUUID", profile.getUUID().toString()); + + startActivityForResult(vprefintent,START_VPN_CONFIG); + } + + private void startVPN(VpnProfile profile) { + + getPM().saveProfile(getActivity(), profile); + + Intent intent = new Intent(getActivity(),LaunchVPN.class); + intent.putExtra(LaunchVPN.EXTRA_KEY, profile.getUUID().toString()); + intent.setAction(Intent.ACTION_MAIN); + startActivity(intent); + } +} diff --git a/app/src/main/java/de/blinkt/openvpn/views/FileSelectLayout.java b/app/src/main/java/de/blinkt/openvpn/views/FileSelectLayout.java new file mode 100644 index 00000000..665b525f --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/views/FileSelectLayout.java @@ -0,0 +1,158 @@ +package de.blinkt.openvpn.views; + +import de.blinkt.openvpn.R; +import de.blinkt.openvpn.VpnProfile; +import de.blinkt.openvpn.activities.FileSelect; +import de.blinkt.openvpn.core.VpnStatus; +import de.blinkt.openvpn.core.X509Utils; +import android.content.Context; +import android.content.Intent; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; +import de.blinkt.openvpn.fragments.Utils; + +import java.io.*; + +import static android.os.Build.*; + + +public class FileSelectLayout extends LinearLayout implements OnClickListener { + + public void parseResponse(Intent data, Context c) { + if (VERSION.SDK_INT < VERSION_CODES.KITKAT) { + String fileData = data.getStringExtra(FileSelect.RESULT_DATA); + setData(fileData, c); + } else if (data != null) { + try { + String newData = Utils.getFilePickerResult(fileType, data, c); + if (newData!=null) + setData(newData, c); + + } catch (IOException e) { + VpnStatus.logException(e); + } catch (SecurityException e) { + VpnStatus.logException(e); + } + + + } + } + + public interface FileSelectCallback { + + String getString(int res); + + void startActivityForResult(Intent startFC, int mTaskId); + } + + private boolean mIsCertificate; + private TextView mDataView; + private String mData; + private FileSelectCallback mFragment; + private int mTaskId; + private Button mSelectButton; + private Utils.FileType fileType; + private String mTitle; + private boolean mShowClear; + private TextView mDataDetails; + + public FileSelectLayout(Context context, AttributeSet attrset) { + super(context, attrset); + + TypedArray ta = context.obtainStyledAttributes(attrset, R.styleable.FileSelectLayout); + + setupViews(ta.getString(R.styleable.FileSelectLayout_title), ta.getBoolean(R.styleable.FileSelectLayout_certificate, true)); + + ta.recycle(); + } + + public FileSelectLayout (Context context, String title, boolean isCerticate) + { + super(context); + + setupViews(title, isCerticate); + + } + + private void setupViews(String title, boolean isCertificate) { + inflate(getContext(), R.layout.file_select, this); + + mTitle = title; + mIsCertificate = isCertificate; + + TextView tview = (TextView) findViewById(R.id.file_title); + tview.setText(mTitle); + + mDataView = (TextView) findViewById(R.id.file_selected_item); + mDataDetails = (TextView) findViewById(R.id.file_selected_description); + mSelectButton = (Button) findViewById(R.id.file_select_button); + mSelectButton.setOnClickListener(this); + } + + + public void setCaller(FileSelectCallback fragment, int i, Utils.FileType ft) { + mTaskId = i; + mFragment = fragment; + fileType = ft; + } + + public void getCertificateFileDialog() { + Intent startFC = new Intent(getContext(), FileSelect.class); + startFC.putExtra(FileSelect.START_DATA, mData); + startFC.putExtra(FileSelect.WINDOW_TITLE, mTitle); + if (fileType == Utils.FileType.PKCS12) + startFC.putExtra(FileSelect.DO_BASE64_ENCODE, true); + if (mShowClear) + startFC.putExtra(FileSelect.SHOW_CLEAR_BUTTON, true); + + mFragment.startActivityForResult(startFC, mTaskId); + } + + + public String getData() { + return mData; + } + + public void setData(String data, Context c) { + mData = data; + if (data == null) { + mDataView.setText(c.getString(R.string.no_data)); + mDataDetails.setText(""); + } else { + if (mData.startsWith(VpnProfile.DISPLAYNAME_TAG)) { + mDataView.setText(c.getString(R.string.imported_from_file, VpnProfile.getDisplayName(mData))); + } else if (mData.startsWith(VpnProfile.INLINE_TAG)) + mDataView.setText(R.string.inline_file_data); + else + mDataView.setText(data); + if (mIsCertificate) + mDataDetails.setText(X509Utils.getCertificateFriendlyName(c, data)); + } + + } + + @Override + public void onClick(View v) { + if (v == mSelectButton) { + if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) { + Intent startFilePicker = Utils.getFilePickerIntent(getContext(),fileType); + mFragment.startActivityForResult(startFilePicker, mTaskId); + } else { + getCertificateFileDialog(); + } + } + } + + + + + public void setShowClear() { + mShowClear = true; + } + +} diff --git a/app/src/main/java/de/blinkt/openvpn/views/RemoteCNPreference.java b/app/src/main/java/de/blinkt/openvpn/views/RemoteCNPreference.java new file mode 100644 index 00000000..388f892b --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/views/RemoteCNPreference.java @@ -0,0 +1,141 @@ +package de.blinkt.openvpn.views; + +import android.content.Context; +import android.preference.DialogPreference; +import android.util.AttributeSet; +import android.util.Pair; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.Spinner; +import android.widget.TextView; + +import de.blinkt.openvpn.R; +import de.blinkt.openvpn.VpnProfile; + +public class RemoteCNPreference extends DialogPreference { + + + private Spinner mSpinner; + private EditText mEditText; + private int mDNType; + private String mDn; + private TextView mRemoteTLSNote; + //private ScrollView mScrollView; + + public RemoteCNPreference(Context context, AttributeSet attrs) { + super(context, attrs); + setDialogLayoutResource(R.layout.tlsremote); + + } + + @Override + protected void onBindDialogView(View view) { + + super.onBindDialogView(view); + + mEditText = (EditText) view.findViewById(R.id.tlsremotecn); + mSpinner = (Spinner) view.findViewById(R.id.x509verifytype); + mRemoteTLSNote = (TextView) view.findViewById(R.id.tlsremotenote); + //mScrollView = (ScrollView) view.findViewById(R.id.tlsremotescroll); + if(mDn!=null) + mEditText.setText(mDn); + + populateSpinner(); + + } + + + + public String getCNText() { + return mDn; + } + + public int getAuthtype() { + return mDNType; + } + + public void setDN(String dn) { + mDn = dn; + if(mEditText!=null) + mEditText.setText(dn); + } + + public void setAuthType(int x509authtype) { + mDNType = x509authtype; + if (mSpinner!=null) + populateSpinner(); + } + + @Override + protected void onDialogClosed(boolean positiveResult) { + super.onDialogClosed(positiveResult); + + if (positiveResult) { + String dn = mEditText.getText().toString(); + int authtype = getAuthTypeFromSpinner(); + if (callChangeListener(new Pair(authtype, dn))) { + mDn = dn; + mDNType = authtype; + } + } + } + + private void populateSpinner() { + ArrayAdapter authtypes = new ArrayAdapter(getContext(), android.R.layout.simple_spinner_item); + authtypes.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + + authtypes.add(getContext().getString(R.string.complete_dn)); + authtypes.add(getContext().getString(R.string.rdn)); + authtypes.add(getContext().getString(R.string.rdn_prefix)); + if ((mDNType == VpnProfile.X509_VERIFY_TLSREMOTE || mDNType == VpnProfile.X509_VERIFY_TLSREMOTE_COMPAT_NOREMAPPING) + && !(mDn==null || "".equals(mDn))) { + authtypes.add(getContext().getString(R.string.tls_remote_deprecated)); + mRemoteTLSNote.setVisibility(View.VISIBLE); + } else { + mRemoteTLSNote.setVisibility(View.GONE); + } + mSpinner.setAdapter(authtypes); + mSpinner.setSelection(getSpinnerPositionFromAuthTYPE()); + } + + private int getSpinnerPositionFromAuthTYPE() { + switch (mDNType) { + case VpnProfile.X509_VERIFY_TLSREMOTE_DN: + return 0; + case VpnProfile.X509_VERIFY_TLSREMOTE_RDN: + return 1; + case VpnProfile.X509_VERIFY_TLSREMOTE_RDN_PREFIX: + return 2; + case VpnProfile.X509_VERIFY_TLSREMOTE_COMPAT_NOREMAPPING: + case VpnProfile.X509_VERIFY_TLSREMOTE: + if (mDn==null || "".equals(mDn)) + return 1; + else + return 3; + + + default: + return 0; + } + } + + private int getAuthTypeFromSpinner() { + int pos = mSpinner.getSelectedItemPosition(); + switch (pos) { + case 0: + return VpnProfile.X509_VERIFY_TLSREMOTE_DN; + case 1: + return VpnProfile.X509_VERIFY_TLSREMOTE_RDN; + case 2: + return VpnProfile.X509_VERIFY_TLSREMOTE_RDN_PREFIX; + case 3: + // This is the tls-remote entry, only visible if mDntype is a + // tls-remote type + return mDNType; + default: + return VpnProfile.X509_VERIFY_TLSREMOTE; + } + } + +} diff --git a/app/src/main/java/de/blinkt/openvpn/views/SeekBarTicks.java b/app/src/main/java/de/blinkt/openvpn/views/SeekBarTicks.java new file mode 100644 index 00000000..88e8e164 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/views/SeekBarTicks.java @@ -0,0 +1,69 @@ +package de.blinkt.openvpn.views; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.ViewConfiguration; +import android.widget.SeekBar; + +public class SeekBarTicks extends SeekBar { + private Paint mTickPaint; + private float mTickHeight; + + private float tickHeightRatio = 0.6f; + + public SeekBarTicks(Context context, AttributeSet attrs) { + super (context, attrs); + + initTicks (context, attrs, android.R.attr.seekBarStyle); + } + + + public SeekBarTicks(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + initTicks (context, attrs, defStyle); + + /*mTickHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + tickHeightDP, + ctx.getResources().getDisplayMetrics()); */ + } + + private void initTicks(Context context, AttributeSet attrs, int defStyle) { + TypedArray a = context.obtainStyledAttributes(attrs, + new int[] { android.R.attr.secondaryProgress }, defStyle, 0); + + + int tickColor = a.getColor(0, android.R.color.black); + mTickPaint = new Paint(); + mTickPaint.setColor( context.getResources().getColor(tickColor)); + a.recycle(); + } + + + @Override + protected synchronized void onDraw(Canvas canvas) { + drawTicks(canvas); + super.onDraw(canvas); + } + + private void drawTicks(Canvas canvas) { + + final int available = getWidth() - getPaddingLeft() - getPaddingRight(); + final int availableHeight = getHeight() - getPaddingBottom() - getPaddingTop(); + + int extrapadding = (int) ((availableHeight- (availableHeight * tickHeightRatio))/2); + + int tickSpacing = available / (getMax() ); + + for (int i = 1; i < getMax(); i++) { + final float x = getPaddingLeft() + i * tickSpacing; + + canvas.drawLine(x, getPaddingTop()+extrapadding, x, getHeight()-getPaddingBottom()-extrapadding, mTickPaint); + } + } +} -- cgit v1.2.3 From 2d0ed30fd65ce6d5832dc857a69c8063cb8b48b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parm=C3=A9nides=20GV?= Date: Wed, 14 May 2014 21:07:23 +0200 Subject: Filter R and BuildConfig to use ours. --- .../blinkt/openvpn/activities/ConfigConverter.java | 2 +- .../blinkt/openvpn/activities/CreateShortcuts.java | 2 +- .../blinkt/openvpn/activities/DisconnectVPN.java | 2 +- .../de/blinkt/openvpn/activities/FileSelect.java | 2 +- .../de/blinkt/openvpn/activities/LogWindow.java | 2 +- .../de/blinkt/openvpn/activities/MainActivity.java | 2 +- .../blinkt/openvpn/activities/VPNPreferences.java | 2 +- .../java/de/blinkt/openvpn/api/APIVpnProfile.java | 102 +- .../java/de/blinkt/openvpn/api/ConfirmDialog.java | 2 +- .../blinkt/openvpn/api/ExternalOpenVPNService.java | 632 +++++------ .../openvpn/api/SecurityRemoteException.java | 24 +- .../blinkt/openvpn/core/DeviceStateReceiver.java | 486 ++++---- .../java/de/blinkt/openvpn/core/NetworkSpace.java | 2 +- .../java/de/blinkt/openvpn/core/OpenVPNThread.java | 348 +++--- .../openvpn/core/OpenVpnManagementThread.java | 1196 ++++++++++---------- .../de/blinkt/openvpn/core/OpenVpnService.java | 6 +- .../de/blinkt/openvpn/core/ProxyDetection.java | 2 +- .../de/blinkt/openvpn/core/VPNLaunchHelper.java | 2 +- .../java/de/blinkt/openvpn/core/VpnStatus.java | 2 +- .../java/de/blinkt/openvpn/core/X509Utils.java | 2 +- .../de/blinkt/openvpn/fragments/AboutFragment.java | 2 +- .../de/blinkt/openvpn/fragments/FaqFragment.java | 2 +- .../openvpn/fragments/FileSelectionFragment.java | 2 +- .../de/blinkt/openvpn/fragments/InlineFileTab.java | 2 +- .../fragments/OpenVpnPreferencesFragment.java | 96 +- .../blinkt/openvpn/fragments/SendDumpFragment.java | 2 +- .../openvpn/fragments/Settings_Authentication.java | 2 +- .../blinkt/openvpn/fragments/Settings_Basic.java | 4 +- .../de/blinkt/openvpn/fragments/Settings_IP.java | 2 +- .../blinkt/openvpn/fragments/Settings_Obscure.java | 2 +- .../blinkt/openvpn/fragments/Settings_Routing.java | 2 +- .../openvpn/fragments/ShowConfigFragment.java | 2 +- .../de/blinkt/openvpn/views/FileSelectLayout.java | 2 +- .../blinkt/openvpn/views/RemoteCNPreference.java | 2 +- 34 files changed, 1472 insertions(+), 1472 deletions(-) (limited to 'app/src/main/java/de/blinkt/openvpn') diff --git a/app/src/main/java/de/blinkt/openvpn/activities/ConfigConverter.java b/app/src/main/java/de/blinkt/openvpn/activities/ConfigConverter.java index f870e8a9..2cb79e05 100644 --- a/app/src/main/java/de/blinkt/openvpn/activities/ConfigConverter.java +++ b/app/src/main/java/de/blinkt/openvpn/activities/ConfigConverter.java @@ -39,7 +39,7 @@ import java.util.List; import java.util.Map; import java.util.Vector; -import de.blinkt.openvpn.R; +import se.leap.bitmaskclient.R; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.ConfigParser; import de.blinkt.openvpn.core.ConfigParser.ConfigParseError; diff --git a/app/src/main/java/de/blinkt/openvpn/activities/CreateShortcuts.java b/app/src/main/java/de/blinkt/openvpn/activities/CreateShortcuts.java index 53a829ff..590b7150 100644 --- a/app/src/main/java/de/blinkt/openvpn/activities/CreateShortcuts.java +++ b/app/src/main/java/de/blinkt/openvpn/activities/CreateShortcuts.java @@ -11,7 +11,7 @@ import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.TextView; import de.blinkt.openvpn.LaunchVPN; -import de.blinkt.openvpn.R; +import se.leap.bitmaskclient.R; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.ProfileManager; diff --git a/app/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java b/app/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java index c2d4c599..da011c98 100644 --- a/app/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java +++ b/app/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java @@ -5,7 +5,7 @@ import android.app.AlertDialog; import android.content.*; import android.os.IBinder; -import de.blinkt.openvpn.R; +import se.leap.bitmaskclient.R; import de.blinkt.openvpn.core.OpenVpnService; import de.blinkt.openvpn.core.ProfileManager; diff --git a/app/src/main/java/de/blinkt/openvpn/activities/FileSelect.java b/app/src/main/java/de/blinkt/openvpn/activities/FileSelect.java index 511dc736..a30b694c 100644 --- a/app/src/main/java/de/blinkt/openvpn/activities/FileSelect.java +++ b/app/src/main/java/de/blinkt/openvpn/activities/FileSelect.java @@ -19,7 +19,7 @@ import android.os.Bundle; import android.os.Environment; import android.util.Base64; -import de.blinkt.openvpn.R; +import se.leap.bitmaskclient.R; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.fragments.FileSelectionFragment; import de.blinkt.openvpn.fragments.InlineFileTab; diff --git a/app/src/main/java/de/blinkt/openvpn/activities/LogWindow.java b/app/src/main/java/de/blinkt/openvpn/activities/LogWindow.java index 27197035..7ed09dd2 100644 --- a/app/src/main/java/de/blinkt/openvpn/activities/LogWindow.java +++ b/app/src/main/java/de/blinkt/openvpn/activities/LogWindow.java @@ -4,7 +4,7 @@ import android.app.Activity; import android.os.Bundle; import android.view.MenuItem; -import de.blinkt.openvpn.R; +import se.leap.bitmaskclient.R; import de.blinkt.openvpn.fragments.LogFragment; /** diff --git a/app/src/main/java/de/blinkt/openvpn/activities/MainActivity.java b/app/src/main/java/de/blinkt/openvpn/activities/MainActivity.java index b32c80cc..26cd0269 100644 --- a/app/src/main/java/de/blinkt/openvpn/activities/MainActivity.java +++ b/app/src/main/java/de/blinkt/openvpn/activities/MainActivity.java @@ -7,7 +7,7 @@ import android.app.Fragment; import android.app.FragmentTransaction; import android.content.Intent; -import de.blinkt.openvpn.R; +import se.leap.bitmaskclient.R; import de.blinkt.openvpn.fragments.*; diff --git a/app/src/main/java/de/blinkt/openvpn/activities/VPNPreferences.java b/app/src/main/java/de/blinkt/openvpn/activities/VPNPreferences.java index 1ebb16b2..1ee9a5f4 100644 --- a/app/src/main/java/de/blinkt/openvpn/activities/VPNPreferences.java +++ b/app/src/main/java/de/blinkt/openvpn/activities/VPNPreferences.java @@ -14,7 +14,7 @@ import android.preference.PreferenceFragment; import android.view.Menu; import android.view.MenuItem; -import de.blinkt.openvpn.R; +import se.leap.bitmaskclient.R; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.ProfileManager; import de.blinkt.openvpn.fragments.Settings_Authentication; diff --git a/app/src/main/java/de/blinkt/openvpn/api/APIVpnProfile.java b/app/src/main/java/de/blinkt/openvpn/api/APIVpnProfile.java index f5591764..a44891ab 100644 --- a/app/src/main/java/de/blinkt/openvpn/api/APIVpnProfile.java +++ b/app/src/main/java/de/blinkt/openvpn/api/APIVpnProfile.java @@ -1,51 +1,51 @@ -package de.blinkt.openvpn.api; - -import android.os.Parcel; -import android.os.Parcelable; - -public class APIVpnProfile implements Parcelable { - - public final String mUUID; - public final String mName; - public final boolean mUserEditable; - - public APIVpnProfile(Parcel in) { - mUUID = in.readString(); - mName = in.readString(); - mUserEditable = in.readInt() != 0; - } - - public APIVpnProfile(String uuidString, String name, boolean userEditable) { - mUUID=uuidString; - mName = name; - mUserEditable=userEditable; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(mUUID); - dest.writeString(mName); - if(mUserEditable) - dest.writeInt(0); - else - dest.writeInt(1); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - public APIVpnProfile createFromParcel(Parcel in) { - return new APIVpnProfile(in); - } - - public APIVpnProfile[] newArray(int size) { - return new APIVpnProfile[size]; - } - }; - - -} +package de.blinkt.openvpn.api; + +import android.os.Parcel; +import android.os.Parcelable; + +public class APIVpnProfile implements Parcelable { + + public final String mUUID; + public final String mName; + public final boolean mUserEditable; + + public APIVpnProfile(Parcel in) { + mUUID = in.readString(); + mName = in.readString(); + mUserEditable = in.readInt() != 0; + } + + public APIVpnProfile(String uuidString, String name, boolean userEditable) { + mUUID=uuidString; + mName = name; + mUserEditable=userEditable; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mUUID); + dest.writeString(mName); + if(mUserEditable) + dest.writeInt(0); + else + dest.writeInt(1); + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public APIVpnProfile createFromParcel(Parcel in) { + return new APIVpnProfile(in); + } + + public APIVpnProfile[] newArray(int size) { + return new APIVpnProfile[size]; + } + }; + + +} diff --git a/app/src/main/java/de/blinkt/openvpn/api/ConfirmDialog.java b/app/src/main/java/de/blinkt/openvpn/api/ConfirmDialog.java index bcab79ed..3856a181 100644 --- a/app/src/main/java/de/blinkt/openvpn/api/ConfirmDialog.java +++ b/app/src/main/java/de/blinkt/openvpn/api/ConfirmDialog.java @@ -29,7 +29,7 @@ import android.widget.Button; import android.widget.CompoundButton; import android.widget.ImageView; import android.widget.TextView; -import de.blinkt.openvpn.R; +import se.leap.bitmaskclient.R; public class ConfirmDialog extends Activity implements diff --git a/app/src/main/java/de/blinkt/openvpn/api/ExternalOpenVPNService.java b/app/src/main/java/de/blinkt/openvpn/api/ExternalOpenVPNService.java index 928a85eb..784ec72a 100644 --- a/app/src/main/java/de/blinkt/openvpn/api/ExternalOpenVPNService.java +++ b/app/src/main/java/de/blinkt/openvpn/api/ExternalOpenVPNService.java @@ -1,317 +1,317 @@ -package de.blinkt.openvpn.api; - -import java.io.IOException; -import java.io.StringReader; -import java.lang.ref.WeakReference; -import java.util.LinkedList; -import java.util.List; - -import android.annotation.TargetApi; -import android.app.Service; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.net.VpnService; -import android.os.*; -import de.blinkt.openvpn.R; -import de.blinkt.openvpn.VpnProfile; -import de.blinkt.openvpn.core.ConfigParser; -import de.blinkt.openvpn.core.ConfigParser.ConfigParseError; -import de.blinkt.openvpn.core.VpnStatus; -import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus; -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 de.blinkt.openvpn.core.VPNLaunchHelper; - -@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) -public class ExternalOpenVPNService extends Service implements StateListener { - - private static final int SEND_TOALL = 0; - - final RemoteCallbackList mCallbacks = - new RemoteCallbackList(); - - private OpenVpnService mService; - private ExternalAppDatabase mExtAppDb; - - - 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; - } - - }; - - @Override - public void onCreate() { - super.onCreate(); - VpnStatus.addStateListener(this); - mExtAppDb = new ExternalAppDatabase(this); - - Intent intent = new Intent(getBaseContext(), OpenVpnService.class); - intent.setAction(OpenVpnService.START_SERVICE); - - bindService(intent, mConnection, Context.BIND_AUTO_CREATE); - mHandler.setService(this); - } - - private final IOpenVPNAPIService.Stub mBinder = new IOpenVPNAPIService.Stub() { - - private void checkOpenVPNPermission() throws SecurityRemoteException { - PackageManager pm = getPackageManager(); - - for (String apppackage : mExtAppDb.getExtAppList()) { - ApplicationInfo app; - try { - app = pm.getApplicationInfo(apppackage, 0); - if (Binder.getCallingUid() == app.uid) { - return; - } - } catch (NameNotFoundException e) { - // App not found. Remove it from the list - mExtAppDb.removeApp(apppackage); - } - - } - throw new SecurityException("Unauthorized OpenVPN API Caller"); - } - - @Override - public List getProfiles() throws RemoteException { - checkOpenVPNPermission(); - - ProfileManager pm = ProfileManager.getInstance(getBaseContext()); - - List profiles = new LinkedList(); - - for (VpnProfile vp : pm.getProfiles()) - profiles.add(new APIVpnProfile(vp.getUUIDString(), vp.mName, vp.mUserEditable)); - - return profiles; - } - - @Override - public void startProfile(String profileUUID) throws RemoteException { - checkOpenVPNPermission(); - - Intent shortVPNIntent = new Intent(Intent.ACTION_MAIN); - shortVPNIntent.setClass(getBaseContext(), de.blinkt.openvpn.LaunchVPN.class); - shortVPNIntent.putExtra(de.blinkt.openvpn.LaunchVPN.EXTRA_KEY, profileUUID); - shortVPNIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(shortVPNIntent); - } - - public void startVPN(String inlineconfig) throws RemoteException { - checkOpenVPNPermission(); - - ConfigParser cp = new ConfigParser(); - try { - cp.parseConfig(new StringReader(inlineconfig)); - VpnProfile vp = cp.convertProfile(); - if (vp.checkProfile(getApplicationContext()) != R.string.no_error_found) - throw new RemoteException(getString(vp.checkProfile(getApplicationContext()))); - - - ProfileManager.setTemporaryProfile(vp); - VPNLaunchHelper.startOpenVpn(vp, getBaseContext()); - - - } catch (IOException e) { - throw new RemoteException(e.getMessage()); - } catch (ConfigParseError e) { - throw new RemoteException(e.getMessage()); - } - } - - @Override - public boolean addVPNProfile(String name, String config) throws RemoteException { - checkOpenVPNPermission(); - - ConfigParser cp = new ConfigParser(); - try { - cp.parseConfig(new StringReader(config)); - VpnProfile vp = cp.convertProfile(); - vp.mName = name; - ProfileManager pm = ProfileManager.getInstance(getBaseContext()); - pm.addProfile(vp); - } catch (IOException e) { - VpnStatus.logException(e); - return false; - } catch (ConfigParseError e) { - VpnStatus.logException(e); - return false; - } - - return true; - } - - - @Override - public Intent prepare(String packagename) { - if (new ExternalAppDatabase(ExternalOpenVPNService.this).isAllowed(packagename)) - return null; - - Intent intent = new Intent(); - intent.setClass(ExternalOpenVPNService.this, ConfirmDialog.class); - return intent; - } - - @Override - public Intent prepareVPNService() throws RemoteException { - checkOpenVPNPermission(); - - if (VpnService.prepare(ExternalOpenVPNService.this) == null) - return null; - else - return new Intent(getBaseContext(), GrantPermissionsActivity.class); - } - - - @Override - public void registerStatusCallback(IOpenVPNStatusCallback cb) - throws RemoteException { - checkOpenVPNPermission(); - - if (cb != null) { - cb.newStatus(mMostRecentState.vpnUUID, mMostRecentState.state, - mMostRecentState.logmessage, mMostRecentState.level.name()); - mCallbacks.register(cb); - } - - - } - - @Override - public void unregisterStatusCallback(IOpenVPNStatusCallback cb) - throws RemoteException { - checkOpenVPNPermission(); - - if (cb != null) - mCallbacks.unregister(cb); - } - - @Override - public void disconnect() throws RemoteException { - checkOpenVPNPermission(); - if (mService != null && mService.getManagement() != null) - mService.getManagement().stopVPN(); - } - - @Override - public void pause() throws RemoteException { - checkOpenVPNPermission(); - if (mService != null) - mService.userPause(true); - } - - @Override - public void resume() throws RemoteException { - checkOpenVPNPermission(); - if (mService != null) - mService.userPause(false); - - } - }; - - - private UpdateMessage mMostRecentState; - - @Override - public IBinder onBind(Intent intent) { - return mBinder; - } - - @Override - public void onDestroy() { - super.onDestroy(); - mCallbacks.kill(); - unbindService(mConnection); - VpnStatus.removeStateListener(this); - } - - class UpdateMessage { - public String state; - public String logmessage; - public ConnectionStatus level; - public String vpnUUID; - - public UpdateMessage(String state, String logmessage, ConnectionStatus level) { - this.state = state; - this.logmessage = logmessage; - this.level = level; - } - } - - @Override - public void updateState(String state, String logmessage, int resid, ConnectionStatus level) { - mMostRecentState = new UpdateMessage(state, logmessage, level); - if (ProfileManager.getLastConnectedVpn() != null) - mMostRecentState.vpnUUID = ProfileManager.getLastConnectedVpn().getUUIDString(); - - Message msg = mHandler.obtainMessage(SEND_TOALL, mMostRecentState); - msg.sendToTarget(); - - } - - private static final OpenVPNServiceHandler mHandler = new OpenVPNServiceHandler(); - - - static class OpenVPNServiceHandler extends Handler { - WeakReference service = null; - - private void setService(ExternalOpenVPNService eos) { - service = new WeakReference(eos); - } - - @Override - public void handleMessage(Message msg) { - - RemoteCallbackList callbacks; - switch (msg.what) { - case SEND_TOALL: - if (service == null || service.get() == null) - return; - - callbacks = service.get().mCallbacks; - - - // Broadcast to all clients the new value. - final int N = callbacks.beginBroadcast(); - for (int i = 0; i < N; i++) { - try { - sendUpdate(callbacks.getBroadcastItem(i), (UpdateMessage) msg.obj); - } catch (RemoteException e) { - // The RemoteCallbackList will take care of removing - // the dead object for us. - } - } - callbacks.finishBroadcast(); - break; - } - } - - private void sendUpdate(IOpenVPNStatusCallback broadcastItem, - UpdateMessage um) throws RemoteException { - broadcastItem.newStatus(um.vpnUUID, um.state, um.logmessage, um.level.name()); - } - } - - +package de.blinkt.openvpn.api; + +import java.io.IOException; +import java.io.StringReader; +import java.lang.ref.WeakReference; +import java.util.LinkedList; +import java.util.List; + +import android.annotation.TargetApi; +import android.app.Service; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.net.VpnService; +import android.os.*; +import se.leap.bitmaskclient.R; +import de.blinkt.openvpn.VpnProfile; +import de.blinkt.openvpn.core.ConfigParser; +import de.blinkt.openvpn.core.ConfigParser.ConfigParseError; +import de.blinkt.openvpn.core.VpnStatus; +import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus; +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 de.blinkt.openvpn.core.VPNLaunchHelper; + +@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) +public class ExternalOpenVPNService extends Service implements StateListener { + + private static final int SEND_TOALL = 0; + + final RemoteCallbackList mCallbacks = + new RemoteCallbackList(); + + private OpenVpnService mService; + private ExternalAppDatabase mExtAppDb; + + + 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; + } + + }; + + @Override + public void onCreate() { + super.onCreate(); + VpnStatus.addStateListener(this); + mExtAppDb = new ExternalAppDatabase(this); + + Intent intent = new Intent(getBaseContext(), OpenVpnService.class); + intent.setAction(OpenVpnService.START_SERVICE); + + bindService(intent, mConnection, Context.BIND_AUTO_CREATE); + mHandler.setService(this); + } + + private final IOpenVPNAPIService.Stub mBinder = new IOpenVPNAPIService.Stub() { + + private void checkOpenVPNPermission() throws SecurityRemoteException { + PackageManager pm = getPackageManager(); + + for (String apppackage : mExtAppDb.getExtAppList()) { + ApplicationInfo app; + try { + app = pm.getApplicationInfo(apppackage, 0); + if (Binder.getCallingUid() == app.uid) { + return; + } + } catch (NameNotFoundException e) { + // App not found. Remove it from the list + mExtAppDb.removeApp(apppackage); + } + + } + throw new SecurityException("Unauthorized OpenVPN API Caller"); + } + + @Override + public List getProfiles() throws RemoteException { + checkOpenVPNPermission(); + + ProfileManager pm = ProfileManager.getInstance(getBaseContext()); + + List profiles = new LinkedList(); + + for (VpnProfile vp : pm.getProfiles()) + profiles.add(new APIVpnProfile(vp.getUUIDString(), vp.mName, vp.mUserEditable)); + + return profiles; + } + + @Override + public void startProfile(String profileUUID) throws RemoteException { + checkOpenVPNPermission(); + + Intent shortVPNIntent = new Intent(Intent.ACTION_MAIN); + shortVPNIntent.setClass(getBaseContext(), de.blinkt.openvpn.LaunchVPN.class); + shortVPNIntent.putExtra(de.blinkt.openvpn.LaunchVPN.EXTRA_KEY, profileUUID); + shortVPNIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(shortVPNIntent); + } + + public void startVPN(String inlineconfig) throws RemoteException { + checkOpenVPNPermission(); + + ConfigParser cp = new ConfigParser(); + try { + cp.parseConfig(new StringReader(inlineconfig)); + VpnProfile vp = cp.convertProfile(); + if (vp.checkProfile(getApplicationContext()) != R.string.no_error_found) + throw new RemoteException(getString(vp.checkProfile(getApplicationContext()))); + + + ProfileManager.setTemporaryProfile(vp); + VPNLaunchHelper.startOpenVpn(vp, getBaseContext()); + + + } catch (IOException e) { + throw new RemoteException(e.getMessage()); + } catch (ConfigParseError e) { + throw new RemoteException(e.getMessage()); + } + } + + @Override + public boolean addVPNProfile(String name, String config) throws RemoteException { + checkOpenVPNPermission(); + + ConfigParser cp = new ConfigParser(); + try { + cp.parseConfig(new StringReader(config)); + VpnProfile vp = cp.convertProfile(); + vp.mName = name; + ProfileManager pm = ProfileManager.getInstance(getBaseContext()); + pm.addProfile(vp); + } catch (IOException e) { + VpnStatus.logException(e); + return false; + } catch (ConfigParseError e) { + VpnStatus.logException(e); + return false; + } + + return true; + } + + + @Override + public Intent prepare(String packagename) { + if (new ExternalAppDatabase(ExternalOpenVPNService.this).isAllowed(packagename)) + return null; + + Intent intent = new Intent(); + intent.setClass(ExternalOpenVPNService.this, ConfirmDialog.class); + return intent; + } + + @Override + public Intent prepareVPNService() throws RemoteException { + checkOpenVPNPermission(); + + if (VpnService.prepare(ExternalOpenVPNService.this) == null) + return null; + else + return new Intent(getBaseContext(), GrantPermissionsActivity.class); + } + + + @Override + public void registerStatusCallback(IOpenVPNStatusCallback cb) + throws RemoteException { + checkOpenVPNPermission(); + + if (cb != null) { + cb.newStatus(mMostRecentState.vpnUUID, mMostRecentState.state, + mMostRecentState.logmessage, mMostRecentState.level.name()); + mCallbacks.register(cb); + } + + + } + + @Override + public void unregisterStatusCallback(IOpenVPNStatusCallback cb) + throws RemoteException { + checkOpenVPNPermission(); + + if (cb != null) + mCallbacks.unregister(cb); + } + + @Override + public void disconnect() throws RemoteException { + checkOpenVPNPermission(); + if (mService != null && mService.getManagement() != null) + mService.getManagement().stopVPN(); + } + + @Override + public void pause() throws RemoteException { + checkOpenVPNPermission(); + if (mService != null) + mService.userPause(true); + } + + @Override + public void resume() throws RemoteException { + checkOpenVPNPermission(); + if (mService != null) + mService.userPause(false); + + } + }; + + + private UpdateMessage mMostRecentState; + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + @Override + public void onDestroy() { + super.onDestroy(); + mCallbacks.kill(); + unbindService(mConnection); + VpnStatus.removeStateListener(this); + } + + class UpdateMessage { + public String state; + public String logmessage; + public ConnectionStatus level; + public String vpnUUID; + + public UpdateMessage(String state, String logmessage, ConnectionStatus level) { + this.state = state; + this.logmessage = logmessage; + this.level = level; + } + } + + @Override + public void updateState(String state, String logmessage, int resid, ConnectionStatus level) { + mMostRecentState = new UpdateMessage(state, logmessage, level); + if (ProfileManager.getLastConnectedVpn() != null) + mMostRecentState.vpnUUID = ProfileManager.getLastConnectedVpn().getUUIDString(); + + Message msg = mHandler.obtainMessage(SEND_TOALL, mMostRecentState); + msg.sendToTarget(); + + } + + private static final OpenVPNServiceHandler mHandler = new OpenVPNServiceHandler(); + + + static class OpenVPNServiceHandler extends Handler { + WeakReference service = null; + + private void setService(ExternalOpenVPNService eos) { + service = new WeakReference(eos); + } + + @Override + public void handleMessage(Message msg) { + + RemoteCallbackList callbacks; + switch (msg.what) { + case SEND_TOALL: + if (service == null || service.get() == null) + return; + + callbacks = service.get().mCallbacks; + + + // Broadcast to all clients the new value. + final int N = callbacks.beginBroadcast(); + for (int i = 0; i < N; i++) { + try { + sendUpdate(callbacks.getBroadcastItem(i), (UpdateMessage) msg.obj); + } catch (RemoteException e) { + // The RemoteCallbackList will take care of removing + // the dead object for us. + } + } + callbacks.finishBroadcast(); + break; + } + } + + private void sendUpdate(IOpenVPNStatusCallback broadcastItem, + UpdateMessage um) throws RemoteException { + broadcastItem.newStatus(um.vpnUUID, um.state, um.logmessage, um.level.name()); + } + } + + } \ No newline at end of file diff --git a/app/src/main/java/de/blinkt/openvpn/api/SecurityRemoteException.java b/app/src/main/java/de/blinkt/openvpn/api/SecurityRemoteException.java index e6011aa3..08d90e3b 100644 --- a/app/src/main/java/de/blinkt/openvpn/api/SecurityRemoteException.java +++ b/app/src/main/java/de/blinkt/openvpn/api/SecurityRemoteException.java @@ -1,12 +1,12 @@ -package de.blinkt.openvpn.api; - -import android.os.RemoteException; - -public class SecurityRemoteException extends RemoteException { - - /** - * - */ - private static final long serialVersionUID = 1L; - -} +package de.blinkt.openvpn.api; + +import android.os.RemoteException; + +public class SecurityRemoteException extends RemoteException { + + /** + * + */ + private static final long serialVersionUID = 1L; + +} diff --git a/app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java b/app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java index 41e8ff0a..18c5f1d9 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java +++ b/app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java @@ -1,243 +1,243 @@ -package de.blinkt.openvpn.core; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.net.NetworkInfo.State; -import android.preference.PreferenceManager; -import de.blinkt.openvpn.R; -import de.blinkt.openvpn.core.VpnStatus.ByteCountListener; - -import java.util.LinkedList; - -import static de.blinkt.openvpn.core.OpenVPNManagement.pauseReason; - -public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountListener { - private int lastNetwork = -1; - private OpenVPNManagement mManagement; - - // Window time in s - private final int TRAFFIC_WINDOW = 60; - // Data traffic limit in bytes - private final long TRAFFIC_LIMIT = 64 * 1024; - - - connectState network = connectState.DISCONNECTED; - connectState screen = connectState.SHOULDBECONNECTED; - connectState userpause = connectState.SHOULDBECONNECTED; - - private String lastStateMsg = null; - - enum connectState { - SHOULDBECONNECTED, - PENDINGDISCONNECT, - DISCONNECTED - } - - static class Datapoint { - private Datapoint(long t, long d) { - timestamp = t; - data = d; - } - - long timestamp; - long data; - } - - LinkedList trafficdata = new LinkedList(); - - @Override - public void updateByteCount(long in, long out, long diffIn, long diffOut) { - if (screen != connectState.PENDINGDISCONNECT) - return; - - long total = diffIn + diffOut; - trafficdata.add(new Datapoint(System.currentTimeMillis(), total)); - - while (trafficdata.getFirst().timestamp <= (System.currentTimeMillis() - TRAFFIC_WINDOW * 1000)) { - trafficdata.removeFirst(); - } - - long windowtraffic = 0; - for (Datapoint dp : trafficdata) - windowtraffic += dp.data; - - if (windowtraffic < TRAFFIC_LIMIT) { - screen = connectState.DISCONNECTED; - VpnStatus.logInfo(R.string.screenoff_pause, - OpenVpnService.humanReadableByteCount(TRAFFIC_LIMIT, false), TRAFFIC_WINDOW); - - mManagement.pause(getPauseReason()); - } - } - - - public void userPause(boolean pause) { - if (pause) { - userpause = connectState.DISCONNECTED; - // Check if we should disconnect - mManagement.pause(getPauseReason()); - } else { - boolean wereConnected = shouldBeConnected(); - userpause = connectState.SHOULDBECONNECTED; - if (shouldBeConnected() && !wereConnected) - mManagement.resume(); - else - // Update the reason why we currently paused - mManagement.pause(getPauseReason()); - } - } - - public DeviceStateReceiver(OpenVPNManagement magnagement) { - super(); - mManagement = magnagement; - } - - - @Override - public void onReceive(Context context, Intent intent) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - - - if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) { - networkStateChange(context); - } else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { - boolean screenOffPause = prefs.getBoolean("screenoff", false); - - if (screenOffPause) { - if (ProfileManager.getLastConnectedVpn()!=null && !ProfileManager.getLastConnectedVpn().mPersistTun) - VpnStatus.logError(R.string.screen_nopersistenttun); - - screen = connectState.PENDINGDISCONNECT; - fillTrafficData(); - if (network == connectState.DISCONNECTED || userpause == connectState.DISCONNECTED) - screen = connectState.DISCONNECTED; - } - } else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) { - // Network was disabled because screen off - boolean connected = shouldBeConnected(); - screen = connectState.SHOULDBECONNECTED; - - /* should be connected has changed because the screen is on now, connect the VPN */ - if (shouldBeConnected() != connected) - mManagement.resume(); - else if (!shouldBeConnected()) - /*Update the reason why we are still paused */ - mManagement.pause(getPauseReason()); - - } - } - - - private void fillTrafficData() { - trafficdata.add(new Datapoint(System.currentTimeMillis(), TRAFFIC_LIMIT)); - } - - - public void networkStateChange(Context context) { - NetworkInfo networkInfo = getCurrentNetworkInfo(context); - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - boolean sendusr1 = prefs.getBoolean("netchangereconnect", true); - - - String netstatestring; - if (networkInfo == null) { - netstatestring = "not connected"; - } else { - String subtype = networkInfo.getSubtypeName(); - if (subtype == null) - subtype = ""; - String extrainfo = networkInfo.getExtraInfo(); - if (extrainfo == null) - extrainfo = ""; - - /* - if(networkInfo.getType()==android.net.ConnectivityManager.TYPE_WIFI) { - WifiManager wifiMgr = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); - WifiInfo wifiinfo = wifiMgr.getConnectionInfo(); - extrainfo+=wifiinfo.getBSSID(); - - subtype += wifiinfo.getNetworkId(); - }*/ - - - netstatestring = String.format("%2$s %4$s to %1$s %3$s", networkInfo.getTypeName(), - networkInfo.getDetailedState(), extrainfo, subtype); - } - - if (networkInfo != null && networkInfo.getState() == State.CONNECTED) { - int newnet = networkInfo.getType(); - network = connectState.SHOULDBECONNECTED; - - if (lastNetwork != newnet) { - if (screen == connectState.PENDINGDISCONNECT) - screen = connectState.DISCONNECTED; - - if (shouldBeConnected()) { - if (sendusr1) { - if (lastNetwork == -1) { - mManagement.resume(); - } else { - mManagement.reconnect(); - } - } else { - mManagement.networkChange(); - } - } - - - lastNetwork = newnet; - } - } else if (networkInfo == null) { - // Not connected, stop openvpn, set last connected network to no network - lastNetwork = -1; - if (sendusr1) { - network = connectState.DISCONNECTED; - - // Set screen state to be disconnected if disconnect pending - if (screen == connectState.PENDINGDISCONNECT) - screen = connectState.DISCONNECTED; - - mManagement.pause(getPauseReason()); - } - } - - - if (!netstatestring.equals(lastStateMsg)) - VpnStatus.logInfo(R.string.netstatus, netstatestring); - lastStateMsg = netstatestring; - - } - - public boolean isUserPaused() { - return userpause == connectState.DISCONNECTED; - } - - private boolean shouldBeConnected() { - return (screen == connectState.SHOULDBECONNECTED && userpause == connectState.SHOULDBECONNECTED && - network == connectState.SHOULDBECONNECTED); - } - - private pauseReason getPauseReason() { - if (userpause == connectState.DISCONNECTED) - return pauseReason.userPause; - - if (screen == connectState.DISCONNECTED) - return pauseReason.screenOff; - - if (network == connectState.DISCONNECTED) - return pauseReason.noNetwork; - - return pauseReason.userPause; - } - - private NetworkInfo getCurrentNetworkInfo(Context context) { - ConnectivityManager conn = (ConnectivityManager) - context.getSystemService(Context.CONNECTIVITY_SERVICE); - - return conn.getActiveNetworkInfo(); - } -} +package de.blinkt.openvpn.core; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.NetworkInfo.State; +import android.preference.PreferenceManager; +import se.leap.bitmaskclient.R; +import de.blinkt.openvpn.core.VpnStatus.ByteCountListener; + +import java.util.LinkedList; + +import static de.blinkt.openvpn.core.OpenVPNManagement.pauseReason; + +public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountListener { + private int lastNetwork = -1; + private OpenVPNManagement mManagement; + + // Window time in s + private final int TRAFFIC_WINDOW = 60; + // Data traffic limit in bytes + private final long TRAFFIC_LIMIT = 64 * 1024; + + + connectState network = connectState.DISCONNECTED; + connectState screen = connectState.SHOULDBECONNECTED; + connectState userpause = connectState.SHOULDBECONNECTED; + + private String lastStateMsg = null; + + enum connectState { + SHOULDBECONNECTED, + PENDINGDISCONNECT, + DISCONNECTED + } + + static class Datapoint { + private Datapoint(long t, long d) { + timestamp = t; + data = d; + } + + long timestamp; + long data; + } + + LinkedList trafficdata = new LinkedList(); + + @Override + public void updateByteCount(long in, long out, long diffIn, long diffOut) { + if (screen != connectState.PENDINGDISCONNECT) + return; + + long total = diffIn + diffOut; + trafficdata.add(new Datapoint(System.currentTimeMillis(), total)); + + while (trafficdata.getFirst().timestamp <= (System.currentTimeMillis() - TRAFFIC_WINDOW * 1000)) { + trafficdata.removeFirst(); + } + + long windowtraffic = 0; + for (Datapoint dp : trafficdata) + windowtraffic += dp.data; + + if (windowtraffic < TRAFFIC_LIMIT) { + screen = connectState.DISCONNECTED; + VpnStatus.logInfo(R.string.screenoff_pause, + OpenVpnService.humanReadableByteCount(TRAFFIC_LIMIT, false), TRAFFIC_WINDOW); + + mManagement.pause(getPauseReason()); + } + } + + + public void userPause(boolean pause) { + if (pause) { + userpause = connectState.DISCONNECTED; + // Check if we should disconnect + mManagement.pause(getPauseReason()); + } else { + boolean wereConnected = shouldBeConnected(); + userpause = connectState.SHOULDBECONNECTED; + if (shouldBeConnected() && !wereConnected) + mManagement.resume(); + else + // Update the reason why we currently paused + mManagement.pause(getPauseReason()); + } + } + + public DeviceStateReceiver(OpenVPNManagement magnagement) { + super(); + mManagement = magnagement; + } + + + @Override + public void onReceive(Context context, Intent intent) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + + + if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) { + networkStateChange(context); + } else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { + boolean screenOffPause = prefs.getBoolean("screenoff", false); + + if (screenOffPause) { + if (ProfileManager.getLastConnectedVpn()!=null && !ProfileManager.getLastConnectedVpn().mPersistTun) + VpnStatus.logError(R.string.screen_nopersistenttun); + + screen = connectState.PENDINGDISCONNECT; + fillTrafficData(); + if (network == connectState.DISCONNECTED || userpause == connectState.DISCONNECTED) + screen = connectState.DISCONNECTED; + } + } else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) { + // Network was disabled because screen off + boolean connected = shouldBeConnected(); + screen = connectState.SHOULDBECONNECTED; + + /* should be connected has changed because the screen is on now, connect the VPN */ + if (shouldBeConnected() != connected) + mManagement.resume(); + else if (!shouldBeConnected()) + /*Update the reason why we are still paused */ + mManagement.pause(getPauseReason()); + + } + } + + + private void fillTrafficData() { + trafficdata.add(new Datapoint(System.currentTimeMillis(), TRAFFIC_LIMIT)); + } + + + public void networkStateChange(Context context) { + NetworkInfo networkInfo = getCurrentNetworkInfo(context); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + boolean sendusr1 = prefs.getBoolean("netchangereconnect", true); + + + String netstatestring; + if (networkInfo == null) { + netstatestring = "not connected"; + } else { + String subtype = networkInfo.getSubtypeName(); + if (subtype == null) + subtype = ""; + String extrainfo = networkInfo.getExtraInfo(); + if (extrainfo == null) + extrainfo = ""; + + /* + if(networkInfo.getType()==android.net.ConnectivityManager.TYPE_WIFI) { + WifiManager wifiMgr = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + WifiInfo wifiinfo = wifiMgr.getConnectionInfo(); + extrainfo+=wifiinfo.getBSSID(); + + subtype += wifiinfo.getNetworkId(); + }*/ + + + netstatestring = String.format("%2$s %4$s to %1$s %3$s", networkInfo.getTypeName(), + networkInfo.getDetailedState(), extrainfo, subtype); + } + + if (networkInfo != null && networkInfo.getState() == State.CONNECTED) { + int newnet = networkInfo.getType(); + network = connectState.SHOULDBECONNECTED; + + if (lastNetwork != newnet) { + if (screen == connectState.PENDINGDISCONNECT) + screen = connectState.DISCONNECTED; + + if (shouldBeConnected()) { + if (sendusr1) { + if (lastNetwork == -1) { + mManagement.resume(); + } else { + mManagement.reconnect(); + } + } else { + mManagement.networkChange(); + } + } + + + lastNetwork = newnet; + } + } else if (networkInfo == null) { + // Not connected, stop openvpn, set last connected network to no network + lastNetwork = -1; + if (sendusr1) { + network = connectState.DISCONNECTED; + + // Set screen state to be disconnected if disconnect pending + if (screen == connectState.PENDINGDISCONNECT) + screen = connectState.DISCONNECTED; + + mManagement.pause(getPauseReason()); + } + } + + + if (!netstatestring.equals(lastStateMsg)) + VpnStatus.logInfo(R.string.netstatus, netstatestring); + lastStateMsg = netstatestring; + + } + + public boolean isUserPaused() { + return userpause == connectState.DISCONNECTED; + } + + private boolean shouldBeConnected() { + return (screen == connectState.SHOULDBECONNECTED && userpause == connectState.SHOULDBECONNECTED && + network == connectState.SHOULDBECONNECTED); + } + + private pauseReason getPauseReason() { + if (userpause == connectState.DISCONNECTED) + return pauseReason.userPause; + + if (screen == connectState.DISCONNECTED) + return pauseReason.screenOff; + + if (network == connectState.DISCONNECTED) + return pauseReason.noNetwork; + + return pauseReason.userPause; + } + + private NetworkInfo getCurrentNetworkInfo(Context context) { + ConnectivityManager conn = (ConnectivityManager) + context.getSystemService(Context.CONNECTIVITY_SERVICE); + + return conn.getActiveNetworkInfo(); + } +} diff --git a/app/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java b/app/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java index afd01184..990e70d8 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java +++ b/app/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java @@ -9,7 +9,7 @@ import java.math.BigInteger; import java.net.Inet6Address; import java.util.*; -import de.blinkt.openvpn.BuildConfig; +import se.leap.bitmaskclient.BuildConfig; public class NetworkSpace { diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java index dacd41c9..67c24884 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java @@ -1,174 +1,174 @@ -package de.blinkt.openvpn.core; - -import android.util.Log; -import de.blinkt.openvpn.R; -import de.blinkt.openvpn.VpnProfile; -import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus; -import de.blinkt.openvpn.core.VpnStatus.LogItem; - -import java.io.*; -import java.text.SimpleDateFormat; -import java.util.*; -import java.util.Map.Entry; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class OpenVPNThread implements Runnable { - private static final String DUMP_PATH_STRING = "Dump path: "; - private static final String TAG = "OpenVPN"; - public static final int M_FATAL = (1 << 4); - public static final int M_NONFATAL = (1 << 5); - public static final int M_WARN = (1 << 6); - public static final int M_DEBUG = (1 << 7); - private String[] mArgv; - private Process mProcess; - private String mNativeDir; - private OpenVpnService mService; - private String mDumpPath; - private Map mProcessEnv; - - public OpenVPNThread(OpenVpnService service,String[] argv, Map processEnv, String nativelibdir) - { - mArgv = argv; - mNativeDir = nativelibdir; - mService = service; - mProcessEnv = processEnv; - } - - public void stopProcess() { - mProcess.destroy(); - } - - - - @Override - public void run() { - try { - Log.i(TAG, "Starting openvpn"); - startOpenVPNThreadArgs(mArgv, mProcessEnv); - Log.i(TAG, "Giving up"); - } catch (Exception e) { - VpnStatus.logException("Starting OpenVPN Thread" ,e); - Log.e(TAG, "OpenVPNThread Got " + e.toString()); - } finally { - int exitvalue = 0; - try { - if (mProcess!=null) - exitvalue = mProcess.waitFor(); - } catch ( IllegalThreadStateException ite) { - VpnStatus.logError("Illegal Thread state: " + ite.getLocalizedMessage()); - } catch (InterruptedException ie) { - VpnStatus.logError("InterruptedException: " + ie.getLocalizedMessage()); - } - if( exitvalue != 0) - VpnStatus.logError("Process exited with exit value " + exitvalue); - - VpnStatus.updateStateString("NOPROCESS", "No process running.", R.string.state_noprocess, ConnectionStatus.LEVEL_NOTCONNECTED); - if(mDumpPath!=null) { - try { - BufferedWriter logout = new BufferedWriter(new FileWriter(mDumpPath + ".log")); - SimpleDateFormat timeformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss",Locale.GERMAN); - for(LogItem li : VpnStatus.getlogbuffer()){ - String time = timeformat.format(new Date(li.getLogtime())); - logout.write(time +" " + li.getString(mService) + "\n"); - } - logout.close(); - VpnStatus.logError(R.string.minidump_generated); - } catch (IOException e) { - VpnStatus.logError("Writing minidump log: " + e.getLocalizedMessage()); - } - } - - mService.processDied(); - Log.i(TAG, "Exiting"); - } - } - - private void startOpenVPNThreadArgs(String[] argv, Map env) { - LinkedList argvlist = new LinkedList(); - - Collections.addAll(argvlist, argv); - - ProcessBuilder pb = new ProcessBuilder(argvlist); - // Hack O rama - - String lbpath = genLibraryPath(argv, pb); - - pb.environment().put("LD_LIBRARY_PATH", lbpath); - - // Add extra variables - for(Entry e:env.entrySet()){ - pb.environment().put(e.getKey(), e.getValue()); - } - pb.redirectErrorStream(true); - try { - mProcess = pb.start(); - // Close the output, since we don't need it - mProcess.getOutputStream().close(); - InputStream in = mProcess.getInputStream(); - BufferedReader br = new BufferedReader(new InputStreamReader(in)); - - while(true) { - String logline = br.readLine(); - if(logline==null) - return; - - if (logline.startsWith(DUMP_PATH_STRING)) - mDumpPath = logline.substring(DUMP_PATH_STRING.length()); - - - // 1380308330.240114 18000002 Send to HTTP proxy: 'X-Online-Host: bla.blabla.com' - - Pattern p = Pattern.compile("(\\d+).(\\d+) ([0-9a-f])+ (.*)"); - Matcher m = p.matcher(logline); - if(m.matches()) { - int flags = Integer.parseInt(m.group(3),16); - String msg = m.group(4); - int logLevel = flags & 0x0F; - - VpnStatus.LogLevel logStatus = VpnStatus.LogLevel.INFO; - - if ((flags & M_FATAL) != 0) - logStatus = VpnStatus.LogLevel.ERROR; - else if ((flags & M_NONFATAL)!=0) - logStatus = VpnStatus.LogLevel.WARNING; - else if ((flags & M_WARN)!=0) - logStatus = VpnStatus.LogLevel.WARNING; - else if ((flags & M_DEBUG)!=0) - logStatus = VpnStatus.LogLevel.VERBOSE; - - if (msg.startsWith("MANAGEMENT: CMD")) - logLevel = Math.max(4, logLevel); - - - VpnStatus.logMessageOpenVPN(logStatus,logLevel,msg); - } else { - VpnStatus.logInfo("P:" + logline); - } - } - - - } catch (IOException e) { - VpnStatus.logException("Error reading from output of OpenVPN process" , e); - stopProcess(); - } - - - } - - private String genLibraryPath(String[] argv, ProcessBuilder pb) { - // Hack until I find a good way to get the real library path - String applibpath = argv[0].replace("/cache/" + VpnProfile.MINIVPN , "/lib"); - - String lbpath = pb.environment().get("LD_LIBRARY_PATH"); - if(lbpath==null) - lbpath = applibpath; - else - lbpath = lbpath + ":" + applibpath; - - if (!applibpath.equals(mNativeDir)) { - lbpath = lbpath + ":" + mNativeDir; - } - return lbpath; - } -} +package de.blinkt.openvpn.core; + +import android.util.Log; +import se.leap.bitmaskclient.R; +import de.blinkt.openvpn.VpnProfile; +import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus; +import de.blinkt.openvpn.core.VpnStatus.LogItem; + +import java.io.*; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class OpenVPNThread implements Runnable { + private static final String DUMP_PATH_STRING = "Dump path: "; + private static final String TAG = "OpenVPN"; + public static final int M_FATAL = (1 << 4); + public static final int M_NONFATAL = (1 << 5); + public static final int M_WARN = (1 << 6); + public static final int M_DEBUG = (1 << 7); + private String[] mArgv; + private Process mProcess; + private String mNativeDir; + private OpenVpnService mService; + private String mDumpPath; + private Map mProcessEnv; + + public OpenVPNThread(OpenVpnService service,String[] argv, Map processEnv, String nativelibdir) + { + mArgv = argv; + mNativeDir = nativelibdir; + mService = service; + mProcessEnv = processEnv; + } + + public void stopProcess() { + mProcess.destroy(); + } + + + + @Override + public void run() { + try { + Log.i(TAG, "Starting openvpn"); + startOpenVPNThreadArgs(mArgv, mProcessEnv); + Log.i(TAG, "Giving up"); + } catch (Exception e) { + VpnStatus.logException("Starting OpenVPN Thread" ,e); + Log.e(TAG, "OpenVPNThread Got " + e.toString()); + } finally { + int exitvalue = 0; + try { + if (mProcess!=null) + exitvalue = mProcess.waitFor(); + } catch ( IllegalThreadStateException ite) { + VpnStatus.logError("Illegal Thread state: " + ite.getLocalizedMessage()); + } catch (InterruptedException ie) { + VpnStatus.logError("InterruptedException: " + ie.getLocalizedMessage()); + } + if( exitvalue != 0) + VpnStatus.logError("Process exited with exit value " + exitvalue); + + VpnStatus.updateStateString("NOPROCESS", "No process running.", R.string.state_noprocess, ConnectionStatus.LEVEL_NOTCONNECTED); + if(mDumpPath!=null) { + try { + BufferedWriter logout = new BufferedWriter(new FileWriter(mDumpPath + ".log")); + SimpleDateFormat timeformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss",Locale.GERMAN); + for(LogItem li : VpnStatus.getlogbuffer()){ + String time = timeformat.format(new Date(li.getLogtime())); + logout.write(time +" " + li.getString(mService) + "\n"); + } + logout.close(); + VpnStatus.logError(R.string.minidump_generated); + } catch (IOException e) { + VpnStatus.logError("Writing minidump log: " + e.getLocalizedMessage()); + } + } + + mService.processDied(); + Log.i(TAG, "Exiting"); + } + } + + private void startOpenVPNThreadArgs(String[] argv, Map env) { + LinkedList argvlist = new LinkedList(); + + Collections.addAll(argvlist, argv); + + ProcessBuilder pb = new ProcessBuilder(argvlist); + // Hack O rama + + String lbpath = genLibraryPath(argv, pb); + + pb.environment().put("LD_LIBRARY_PATH", lbpath); + + // Add extra variables + for(Entry e:env.entrySet()){ + pb.environment().put(e.getKey(), e.getValue()); + } + pb.redirectErrorStream(true); + try { + mProcess = pb.start(); + // Close the output, since we don't need it + mProcess.getOutputStream().close(); + InputStream in = mProcess.getInputStream(); + BufferedReader br = new BufferedReader(new InputStreamReader(in)); + + while(true) { + String logline = br.readLine(); + if(logline==null) + return; + + if (logline.startsWith(DUMP_PATH_STRING)) + mDumpPath = logline.substring(DUMP_PATH_STRING.length()); + + + // 1380308330.240114 18000002 Send to HTTP proxy: 'X-Online-Host: bla.blabla.com' + + Pattern p = Pattern.compile("(\\d+).(\\d+) ([0-9a-f])+ (.*)"); + Matcher m = p.matcher(logline); + if(m.matches()) { + int flags = Integer.parseInt(m.group(3),16); + String msg = m.group(4); + int logLevel = flags & 0x0F; + + VpnStatus.LogLevel logStatus = VpnStatus.LogLevel.INFO; + + if ((flags & M_FATAL) != 0) + logStatus = VpnStatus.LogLevel.ERROR; + else if ((flags & M_NONFATAL)!=0) + logStatus = VpnStatus.LogLevel.WARNING; + else if ((flags & M_WARN)!=0) + logStatus = VpnStatus.LogLevel.WARNING; + else if ((flags & M_DEBUG)!=0) + logStatus = VpnStatus.LogLevel.VERBOSE; + + if (msg.startsWith("MANAGEMENT: CMD")) + logLevel = Math.max(4, logLevel); + + + VpnStatus.logMessageOpenVPN(logStatus,logLevel,msg); + } else { + VpnStatus.logInfo("P:" + logline); + } + } + + + } catch (IOException e) { + VpnStatus.logException("Error reading from output of OpenVPN process" , e); + stopProcess(); + } + + + } + + private String genLibraryPath(String[] argv, ProcessBuilder pb) { + // Hack until I find a good way to get the real library path + String applibpath = argv[0].replace("/cache/" + VpnProfile.MINIVPN , "/lib"); + + String lbpath = pb.environment().get("LD_LIBRARY_PATH"); + if(lbpath==null) + lbpath = applibpath; + else + lbpath = lbpath + ":" + applibpath; + + if (!applibpath.equals(mNativeDir)) { + lbpath = lbpath + ":" + mNativeDir; + } + return lbpath; + } +} diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java index 6ef41e1b..4cba4f5f 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java @@ -1,598 +1,598 @@ -package de.blinkt.openvpn.core; - -import android.content.Context; -import android.content.SharedPreferences; -import android.net.LocalServerSocket; -import android.net.LocalSocket; -import android.net.LocalSocketAddress; -import android.os.ParcelFileDescriptor; -import android.preference.PreferenceManager; -import android.util.Log; - -import org.jetbrains.annotations.NotNull; - -import de.blinkt.openvpn.R; -import de.blinkt.openvpn.VpnProfile; -import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus; - -import java.io.FileDescriptor; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.util.*; - -public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { - - private static final String TAG = "openvpn"; - private LocalSocket mSocket; - private VpnProfile mProfile; - private OpenVpnService mOpenVPNService; - private LinkedList mFDList=new LinkedList(); - private LocalServerSocket mServerSocket; - private boolean mReleaseHold=true; - private boolean mWaitingForRelease=false; - private long mLastHoldRelease=0; - - private static Vector active=new Vector(); - private LocalSocket mServerSocketLocal; - - private pauseReason lastPauseReason = pauseReason.noNetwork; - - public OpenVpnManagementThread(VpnProfile profile, OpenVpnService openVpnService) { - mProfile = profile; - mOpenVPNService = openVpnService; - - - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(openVpnService); - boolean managemeNetworkState = prefs.getBoolean("netchangereconnect", true); - if(managemeNetworkState) - mReleaseHold=false; - - } - - public boolean openManagementInterface(@NotNull Context c) { - // Could take a while to open connection - int tries=8; - - String socketName = (c.getCacheDir().getAbsolutePath() + "/" + "mgmtsocket"); - // The mServerSocketLocal is transferred to the LocalServerSocket, ignore warning - - mServerSocketLocal = new LocalSocket(); - - while(tries > 0 && !mServerSocketLocal.isConnected()) { - try { - mServerSocketLocal.bind(new LocalSocketAddress(socketName, - LocalSocketAddress.Namespace.FILESYSTEM)); - } catch (IOException e) { - // wait 300 ms before retrying - try { Thread.sleep(300); - } catch (InterruptedException e1) { - } - - } - tries--; - } - - try { - - mServerSocket = new LocalServerSocket(mServerSocketLocal.getFileDescriptor()); - return true; - } catch (IOException e) { - VpnStatus.logException(e); - } - return false; - - - } - - public void managmentCommand(String cmd) { - try { - if(mSocket!=null && mSocket.getOutputStream() !=null) { - mSocket.getOutputStream().write(cmd.getBytes()); - mSocket.getOutputStream().flush(); - } - }catch (IOException e) { - // Ignore socket stack traces - } - } - - - @Override - public void run() { - byte [] buffer =new byte[2048]; - // mSocket.setSoTimeout(5); // Setting a timeout cannot be that bad - - String pendingInput=""; - active.add(this); - - try { - // Wait for a client to connect - mSocket= mServerSocket.accept(); - InputStream instream = mSocket.getInputStream(); - // Close the management socket after client connected - - mServerSocket.close(); - // Closing one of the two sockets also closes the other - //mServerSocketLocal.close(); - - while(true) { - int numbytesread = instream.read(buffer); - if(numbytesread==-1) - return; - - FileDescriptor[] fds = null; - try { - fds = mSocket.getAncillaryFileDescriptors(); - } catch (IOException e) { - VpnStatus.logException("Error reading fds from socket", e); - } - if(fds!=null){ - Collections.addAll(mFDList, fds); - } - - String input = new String(buffer,0,numbytesread,"UTF-8"); - - pendingInput += input; - - pendingInput=processInput(pendingInput); - - - - } - } catch (IOException e) { - if (!e.getMessage().equals("socket closed")) - VpnStatus.logException(e); - } - active.remove(this); - } - - //! Hack O Rama 2000! - private void protectFileDescriptor(FileDescriptor fd) { - Exception exp; - try { - Method getInt = FileDescriptor.class.getDeclaredMethod("getInt$"); - int fdint = (Integer) getInt.invoke(fd); - - // You can even get more evil by parsing toString() and extract the int from that :) - - boolean result = mOpenVPNService.protect(fdint); - if (!result) - VpnStatus.logWarning("Could not protect VPN socket"); - - - //ParcelFileDescriptor pfd = ParcelFileDescriptor.fromFd(fdint); - //pfd.close(); - NativeUtils.jniclose(fdint); - return; - } catch (NoSuchMethodException e) { - exp =e; - } catch (IllegalArgumentException e) { - exp =e; - } catch (IllegalAccessException e) { - exp =e; - } catch (InvocationTargetException e) { - exp =e; - } catch (NullPointerException e) { - exp =e; - } - - Log.d("Openvpn", "Failed to retrieve fd from socket: " + fd); - VpnStatus.logException("Failed to retrieve fd from socket (" + fd + ")" , exp); - } - - private String processInput(String pendingInput) { - - - while(pendingInput.contains("\n")) { - String[] tokens = pendingInput.split("\\r?\\n", 2); - processCommand(tokens[0]); - if(tokens.length == 1) - // No second part, newline was at the end - pendingInput=""; - else - pendingInput=tokens[1]; - } - return pendingInput; - } - - - private void processCommand(String command) { - //Log.i(TAG, "Line from managment" + command); - - - if (command.startsWith(">") && command.contains(":")) { - String[] parts = command.split(":",2); - String cmd = parts[0].substring(1); - String argument = parts[1]; - - - if(cmd.equals("INFO")) { - /* Ignore greeting from management */ - return; - }else if (cmd.equals("PASSWORD")) { - processPWCommand(argument); - } else if (cmd.equals("HOLD")) { - handleHold(); - } else if (cmd.equals("NEED-OK")) { - processNeedCommand(argument); - } else if (cmd.equals("BYTECOUNT")){ - processByteCount(argument); - } else if (cmd.equals("STATE")) { - processState(argument); - } else if (cmd.equals("PROXY")) { - processProxyCMD(argument); - } else if (cmd.equals("LOG")) { - processLogMessage(argument); - } else if (cmd.equals("RSA_SIGN")) { - processSignCommand(argument); - } else { - VpnStatus.logWarning("MGMT: Got unrecognized command" + command); - Log.i(TAG, "Got unrecognized command" + command); - } - } else if (command.startsWith("SUCCESS:")) { - /* Ignore this kind of message too */ - return; - } else if (command.startsWith("PROTECTFD: ")) { - FileDescriptor fdtoprotect = mFDList.pollFirst(); - if (fdtoprotect!=null) - protectFileDescriptor(fdtoprotect); - } else { - Log.i(TAG, "Got unrecognized line from managment" + command); - VpnStatus.logWarning("MGMT: Got unrecognized line from management:" + command); - } - } - - private void processLogMessage(String argument) { - String[] args = argument.split(",",4); - // 0 unix time stamp - // 1 log level N,I,E etc. - /* - (b) zero or more message flags in a single string: - I -- informational - F -- fatal error - N -- non-fatal error - W -- warning - D -- debug, and - */ - // 2 log message - - Log.d("OpenVPN", argument); - - VpnStatus.LogLevel level; - if (args[1].equals("I")) { - level = VpnStatus.LogLevel.INFO; - } else if (args[1].equals("W")) { - level = VpnStatus.LogLevel.WARNING; - } else if (args[1].equals("D")) { - level = VpnStatus.LogLevel.VERBOSE; - } else if (args[1].equals("F")) { - level = VpnStatus.LogLevel.ERROR; - } else { - level = VpnStatus.LogLevel.INFO; - } - - int ovpnlevel = Integer.parseInt(args[2]) & 0x0F; - String msg = args[3]; - - if (msg.startsWith("MANAGEMENT: CMD")) - ovpnlevel = Math.max(4, ovpnlevel); - - VpnStatus.logMessageOpenVPN(level,ovpnlevel, msg); - } - - private void handleHold() { - if(mReleaseHold) { - releaseHoldCmd(); - } else { - mWaitingForRelease=true; - - VpnStatus.updateStatePause(lastPauseReason); - - - } - } - private void releaseHoldCmd() { - if ((System.currentTimeMillis()- mLastHoldRelease) < 5000) { - try { - Thread.sleep(3000); - } catch (InterruptedException e) { - } - - } - mWaitingForRelease=false; - mLastHoldRelease = System.currentTimeMillis(); - managmentCommand("hold release\n"); - managmentCommand("bytecount " + mBytecountInterval + "\n"); - managmentCommand("state on\n"); - //managmentCommand("log on all\n"); - } - - public void releaseHold() { - mReleaseHold=true; - if(mWaitingForRelease) - releaseHoldCmd(); - - } - - private void processProxyCMD(String argument) { - String[] args = argument.split(",",3); - SocketAddress proxyaddr = ProxyDetection.detectProxy(mProfile); - - - if(args.length >= 2) { - String proto = args[1]; - if(proto.equals("UDP")) { - proxyaddr=null; - } - } - - if(proxyaddr instanceof InetSocketAddress ){ - InetSocketAddress isa = (InetSocketAddress) proxyaddr; - - VpnStatus.logInfo(R.string.using_proxy, isa.getHostName(), isa.getPort()); - - String proxycmd = String.format(Locale.ENGLISH,"proxy HTTP %s %d\n", isa.getHostName(),isa.getPort()); - managmentCommand(proxycmd); - } else { - managmentCommand("proxy NONE\n"); - } - - } - private void processState(String argument) { - String[] args = argument.split(",",3); - String currentstate = args[1]; - - if(args[2].equals(",,")) - VpnStatus.updateStateString(currentstate, ""); - else - VpnStatus.updateStateString(currentstate, args[2]); - } - - - private void processByteCount(String argument) { - // >BYTECOUNT:{BYTES_IN},{BYTES_OUT} - int comma = argument.indexOf(','); - long in = Long.parseLong(argument.substring(0, comma)); - long out = Long.parseLong(argument.substring(comma+1)); - - VpnStatus.updateByteCount(in, out); - - } - - - - private void processNeedCommand(String argument) { - int p1 =argument.indexOf('\''); - int p2 = argument.indexOf('\'',p1+1); - - String needed = argument.substring(p1+1, p2); - String extra = argument.split(":",2)[1]; - - String status = "ok"; - - - if (needed.equals("PROTECTFD")) { - FileDescriptor fdtoprotect = mFDList.pollFirst(); - protectFileDescriptor(fdtoprotect); - } else if (needed.equals("DNSSERVER")) { - mOpenVPNService.addDNS(extra); - }else if (needed.equals("DNSDOMAIN")){ - mOpenVPNService.setDomain(extra); - } else if (needed.equals("ROUTE")) { - String[] routeparts = extra.split(" "); - - /* - buf_printf (&out, "%s %s %s dev %s", network, netmask, gateway, rgi->iface); - else - buf_printf (&out, "%s %s %s", network, netmask, gateway); - */ - - if(routeparts.length==5) { - assert(routeparts[3].equals("dev")); - mOpenVPNService.addRoute(routeparts[0], routeparts[1], routeparts[2], routeparts[4]); - } else if (routeparts.length >= 3) { - mOpenVPNService.addRoute(routeparts[0], routeparts[1], routeparts[2], null); - } else { - VpnStatus.logError("Unrecognized ROUTE cmd:" + Arrays.toString(routeparts) + " | " + argument); - } - - } else if (needed.equals("ROUTE6")) { - String[] routeparts = extra.split(" "); - mOpenVPNService.addRoutev6(routeparts[0],routeparts[1]); - } else if (needed.equals("IFCONFIG")) { - String[] ifconfigparts = extra.split(" "); - int mtu = Integer.parseInt(ifconfigparts[2]); - mOpenVPNService.setLocalIP(ifconfigparts[0], ifconfigparts[1],mtu,ifconfigparts[3]); - } else if (needed.equals("IFCONFIG6")) { - mOpenVPNService.setLocalIPv6(extra); - - } else if (needed.equals("PERSIST_TUN_ACTION")) { - // check if tun cfg stayed the same - status = mOpenVPNService.getTunReopenStatus(); - } else if (needed.equals("OPENTUN")) { - if(sendTunFD(needed,extra)) - return; - else - status="cancel"; - // This not nice or anything but setFileDescriptors accepts only FilDescriptor class :( - - } else { - Log.e(TAG,"Unkown needok command " + argument); - return; - } - - String cmd = String.format("needok '%s' %s\n", needed, status); - managmentCommand(cmd); - } - - private boolean sendTunFD (String needed, String extra) { - Exception exp; - if(!extra.equals("tun")) { - // We only support tun - VpnStatus.logError(String.format("Device type %s requested, but only tun is possible with the Android API, sorry!",extra)); - - return false; - } - ParcelFileDescriptor pfd = mOpenVPNService.openTun(); - if(pfd==null) - return false; - - Method setInt; - int fdint = pfd.getFd(); - try { - setInt = FileDescriptor.class.getDeclaredMethod("setInt$",int.class); - FileDescriptor fdtosend = new FileDescriptor(); - - setInt.invoke(fdtosend,fdint); - - FileDescriptor[] fds = {fdtosend}; - mSocket.setFileDescriptorsForSend(fds); - - // Trigger a send so we can close the fd on our side of the channel - // The API documentation fails to mention that it will not reset the file descriptor to - // be send and will happily send the file descriptor on every write ... - String cmd = String.format("needok '%s' %s\n", needed, "ok"); - managmentCommand(cmd); - - // Set the FileDescriptor to null to stop this mad behavior - mSocket.setFileDescriptorsForSend(null); - - pfd.close(); - - return true; - } catch (NoSuchMethodException e) { - exp =e; - } catch (IllegalArgumentException e) { - exp =e; - } catch (IllegalAccessException e) { - exp =e; - } catch (InvocationTargetException e) { - exp =e; - } catch (IOException e) { - exp =e; - } - VpnStatus.logException("Could not send fd over socket" , exp); - - return false; - } - - private void processPWCommand(String argument) { - //argument has the form Need 'Private Key' password - // or ">PASSWORD:Verification Failed: '%s' ['%s']" - String needed; - - - - try{ - - int p1 = argument.indexOf('\''); - int p2 = argument.indexOf('\'',p1+1); - needed = argument.substring(p1+1, p2); - if (argument.startsWith("Verification Failed")) { - proccessPWFailed(needed, argument.substring(p2+1)); - return; - } - } catch (StringIndexOutOfBoundsException sioob) { - VpnStatus.logError("Could not parse management Password command: " + argument); - return; - } - - String pw=null; - - if(needed.equals("Private Key")) { - pw = mProfile.getPasswordPrivateKey(); - } else if (needed.equals("Auth")) { - String usercmd = String.format("username '%s' %s\n", - needed, VpnProfile.openVpnEscape(mProfile.mUsername)); - managmentCommand(usercmd); - pw = mProfile.getPasswordAuth(); - } - if(pw!=null) { - String cmd = String.format("password '%s' %s\n", needed, VpnProfile.openVpnEscape(pw)); - managmentCommand(cmd); - } else { - VpnStatus.logError(String.format("Openvpn requires Authentication type '%s' but no password/key information available", needed)); - } - - } - - - - - private void proccessPWFailed(String needed, String args) { - VpnStatus.updateStateString("AUTH_FAILED", needed + args, R.string.state_auth_failed, ConnectionStatus.LEVEL_AUTH_FAILED); - } - - - private static boolean stopOpenVPN() { - boolean sendCMD=false; - for (OpenVpnManagementThread mt: active){ - mt.managmentCommand("signal SIGINT\n"); - sendCMD=true; - try { - if(mt.mSocket !=null) - mt.mSocket.close(); - } catch (IOException e) { - // Ignore close error on already closed socket - } - } - return sendCMD; - } - - @Override - public void networkChange() { - if(!mWaitingForRelease) - managmentCommand("network-change\n"); - } - - public void signalusr1() { - mReleaseHold=false; - - if(!mWaitingForRelease) - managmentCommand("signal SIGUSR1\n"); - else - // If signalusr1 is called update the state string - // if there is another for stopping - VpnStatus.updateStatePause(lastPauseReason); - } - - public void reconnect() { - signalusr1(); - releaseHold(); - } - - private void processSignCommand(String b64data) { - - String signed_string = mProfile.getSignedData(b64data); - if(signed_string==null) { - managmentCommand("rsa-sig\n"); - managmentCommand("\nEND\n"); - stopOpenVPN(); - return; - } - managmentCommand("rsa-sig\n"); - managmentCommand(signed_string); - managmentCommand("\nEND\n"); - } - - @Override - public void pause (pauseReason reason) { - lastPauseReason = reason; - signalusr1(); - } - - @Override - public void resume() { - releaseHold(); - /* Reset the reason why we are disconnected */ - lastPauseReason = pauseReason.noNetwork; - } - - @Override - public boolean stopVPN() { - return stopOpenVPN(); - } -} +package de.blinkt.openvpn.core; + +import android.content.Context; +import android.content.SharedPreferences; +import android.net.LocalServerSocket; +import android.net.LocalSocket; +import android.net.LocalSocketAddress; +import android.os.ParcelFileDescriptor; +import android.preference.PreferenceManager; +import android.util.Log; + +import org.jetbrains.annotations.NotNull; + +import se.leap.bitmaskclient.R; +import de.blinkt.openvpn.VpnProfile; +import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.*; + +public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { + + private static final String TAG = "openvpn"; + private LocalSocket mSocket; + private VpnProfile mProfile; + private OpenVpnService mOpenVPNService; + private LinkedList mFDList=new LinkedList(); + private LocalServerSocket mServerSocket; + private boolean mReleaseHold=true; + private boolean mWaitingForRelease=false; + private long mLastHoldRelease=0; + + private static Vector active=new Vector(); + private LocalSocket mServerSocketLocal; + + private pauseReason lastPauseReason = pauseReason.noNetwork; + + public OpenVpnManagementThread(VpnProfile profile, OpenVpnService openVpnService) { + mProfile = profile; + mOpenVPNService = openVpnService; + + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(openVpnService); + boolean managemeNetworkState = prefs.getBoolean("netchangereconnect", true); + if(managemeNetworkState) + mReleaseHold=false; + + } + + public boolean openManagementInterface(@NotNull Context c) { + // Could take a while to open connection + int tries=8; + + String socketName = (c.getCacheDir().getAbsolutePath() + "/" + "mgmtsocket"); + // The mServerSocketLocal is transferred to the LocalServerSocket, ignore warning + + mServerSocketLocal = new LocalSocket(); + + while(tries > 0 && !mServerSocketLocal.isConnected()) { + try { + mServerSocketLocal.bind(new LocalSocketAddress(socketName, + LocalSocketAddress.Namespace.FILESYSTEM)); + } catch (IOException e) { + // wait 300 ms before retrying + try { Thread.sleep(300); + } catch (InterruptedException e1) { + } + + } + tries--; + } + + try { + + mServerSocket = new LocalServerSocket(mServerSocketLocal.getFileDescriptor()); + return true; + } catch (IOException e) { + VpnStatus.logException(e); + } + return false; + + + } + + public void managmentCommand(String cmd) { + try { + if(mSocket!=null && mSocket.getOutputStream() !=null) { + mSocket.getOutputStream().write(cmd.getBytes()); + mSocket.getOutputStream().flush(); + } + }catch (IOException e) { + // Ignore socket stack traces + } + } + + + @Override + public void run() { + byte [] buffer =new byte[2048]; + // mSocket.setSoTimeout(5); // Setting a timeout cannot be that bad + + String pendingInput=""; + active.add(this); + + try { + // Wait for a client to connect + mSocket= mServerSocket.accept(); + InputStream instream = mSocket.getInputStream(); + // Close the management socket after client connected + + mServerSocket.close(); + // Closing one of the two sockets also closes the other + //mServerSocketLocal.close(); + + while(true) { + int numbytesread = instream.read(buffer); + if(numbytesread==-1) + return; + + FileDescriptor[] fds = null; + try { + fds = mSocket.getAncillaryFileDescriptors(); + } catch (IOException e) { + VpnStatus.logException("Error reading fds from socket", e); + } + if(fds!=null){ + Collections.addAll(mFDList, fds); + } + + String input = new String(buffer,0,numbytesread,"UTF-8"); + + pendingInput += input; + + pendingInput=processInput(pendingInput); + + + + } + } catch (IOException e) { + if (!e.getMessage().equals("socket closed")) + VpnStatus.logException(e); + } + active.remove(this); + } + + //! Hack O Rama 2000! + private void protectFileDescriptor(FileDescriptor fd) { + Exception exp; + try { + Method getInt = FileDescriptor.class.getDeclaredMethod("getInt$"); + int fdint = (Integer) getInt.invoke(fd); + + // You can even get more evil by parsing toString() and extract the int from that :) + + boolean result = mOpenVPNService.protect(fdint); + if (!result) + VpnStatus.logWarning("Could not protect VPN socket"); + + + //ParcelFileDescriptor pfd = ParcelFileDescriptor.fromFd(fdint); + //pfd.close(); + NativeUtils.jniclose(fdint); + return; + } catch (NoSuchMethodException e) { + exp =e; + } catch (IllegalArgumentException e) { + exp =e; + } catch (IllegalAccessException e) { + exp =e; + } catch (InvocationTargetException e) { + exp =e; + } catch (NullPointerException e) { + exp =e; + } + + Log.d("Openvpn", "Failed to retrieve fd from socket: " + fd); + VpnStatus.logException("Failed to retrieve fd from socket (" + fd + ")" , exp); + } + + private String processInput(String pendingInput) { + + + while(pendingInput.contains("\n")) { + String[] tokens = pendingInput.split("\\r?\\n", 2); + processCommand(tokens[0]); + if(tokens.length == 1) + // No second part, newline was at the end + pendingInput=""; + else + pendingInput=tokens[1]; + } + return pendingInput; + } + + + private void processCommand(String command) { + //Log.i(TAG, "Line from managment" + command); + + + if (command.startsWith(">") && command.contains(":")) { + String[] parts = command.split(":",2); + String cmd = parts[0].substring(1); + String argument = parts[1]; + + + if(cmd.equals("INFO")) { + /* Ignore greeting from management */ + return; + }else if (cmd.equals("PASSWORD")) { + processPWCommand(argument); + } else if (cmd.equals("HOLD")) { + handleHold(); + } else if (cmd.equals("NEED-OK")) { + processNeedCommand(argument); + } else if (cmd.equals("BYTECOUNT")){ + processByteCount(argument); + } else if (cmd.equals("STATE")) { + processState(argument); + } else if (cmd.equals("PROXY")) { + processProxyCMD(argument); + } else if (cmd.equals("LOG")) { + processLogMessage(argument); + } else if (cmd.equals("RSA_SIGN")) { + processSignCommand(argument); + } else { + VpnStatus.logWarning("MGMT: Got unrecognized command" + command); + Log.i(TAG, "Got unrecognized command" + command); + } + } else if (command.startsWith("SUCCESS:")) { + /* Ignore this kind of message too */ + return; + } else if (command.startsWith("PROTECTFD: ")) { + FileDescriptor fdtoprotect = mFDList.pollFirst(); + if (fdtoprotect!=null) + protectFileDescriptor(fdtoprotect); + } else { + Log.i(TAG, "Got unrecognized line from managment" + command); + VpnStatus.logWarning("MGMT: Got unrecognized line from management:" + command); + } + } + + private void processLogMessage(String argument) { + String[] args = argument.split(",",4); + // 0 unix time stamp + // 1 log level N,I,E etc. + /* + (b) zero or more message flags in a single string: + I -- informational + F -- fatal error + N -- non-fatal error + W -- warning + D -- debug, and + */ + // 2 log message + + Log.d("OpenVPN", argument); + + VpnStatus.LogLevel level; + if (args[1].equals("I")) { + level = VpnStatus.LogLevel.INFO; + } else if (args[1].equals("W")) { + level = VpnStatus.LogLevel.WARNING; + } else if (args[1].equals("D")) { + level = VpnStatus.LogLevel.VERBOSE; + } else if (args[1].equals("F")) { + level = VpnStatus.LogLevel.ERROR; + } else { + level = VpnStatus.LogLevel.INFO; + } + + int ovpnlevel = Integer.parseInt(args[2]) & 0x0F; + String msg = args[3]; + + if (msg.startsWith("MANAGEMENT: CMD")) + ovpnlevel = Math.max(4, ovpnlevel); + + VpnStatus.logMessageOpenVPN(level,ovpnlevel, msg); + } + + private void handleHold() { + if(mReleaseHold) { + releaseHoldCmd(); + } else { + mWaitingForRelease=true; + + VpnStatus.updateStatePause(lastPauseReason); + + + } + } + private void releaseHoldCmd() { + if ((System.currentTimeMillis()- mLastHoldRelease) < 5000) { + try { + Thread.sleep(3000); + } catch (InterruptedException e) { + } + + } + mWaitingForRelease=false; + mLastHoldRelease = System.currentTimeMillis(); + managmentCommand("hold release\n"); + managmentCommand("bytecount " + mBytecountInterval + "\n"); + managmentCommand("state on\n"); + //managmentCommand("log on all\n"); + } + + public void releaseHold() { + mReleaseHold=true; + if(mWaitingForRelease) + releaseHoldCmd(); + + } + + private void processProxyCMD(String argument) { + String[] args = argument.split(",",3); + SocketAddress proxyaddr = ProxyDetection.detectProxy(mProfile); + + + if(args.length >= 2) { + String proto = args[1]; + if(proto.equals("UDP")) { + proxyaddr=null; + } + } + + if(proxyaddr instanceof InetSocketAddress ){ + InetSocketAddress isa = (InetSocketAddress) proxyaddr; + + VpnStatus.logInfo(R.string.using_proxy, isa.getHostName(), isa.getPort()); + + String proxycmd = String.format(Locale.ENGLISH,"proxy HTTP %s %d\n", isa.getHostName(),isa.getPort()); + managmentCommand(proxycmd); + } else { + managmentCommand("proxy NONE\n"); + } + + } + private void processState(String argument) { + String[] args = argument.split(",",3); + String currentstate = args[1]; + + if(args[2].equals(",,")) + VpnStatus.updateStateString(currentstate, ""); + else + VpnStatus.updateStateString(currentstate, args[2]); + } + + + private void processByteCount(String argument) { + // >BYTECOUNT:{BYTES_IN},{BYTES_OUT} + int comma = argument.indexOf(','); + long in = Long.parseLong(argument.substring(0, comma)); + long out = Long.parseLong(argument.substring(comma+1)); + + VpnStatus.updateByteCount(in, out); + + } + + + + private void processNeedCommand(String argument) { + int p1 =argument.indexOf('\''); + int p2 = argument.indexOf('\'',p1+1); + + String needed = argument.substring(p1+1, p2); + String extra = argument.split(":",2)[1]; + + String status = "ok"; + + + if (needed.equals("PROTECTFD")) { + FileDescriptor fdtoprotect = mFDList.pollFirst(); + protectFileDescriptor(fdtoprotect); + } else if (needed.equals("DNSSERVER")) { + mOpenVPNService.addDNS(extra); + }else if (needed.equals("DNSDOMAIN")){ + mOpenVPNService.setDomain(extra); + } else if (needed.equals("ROUTE")) { + String[] routeparts = extra.split(" "); + + /* + buf_printf (&out, "%s %s %s dev %s", network, netmask, gateway, rgi->iface); + else + buf_printf (&out, "%s %s %s", network, netmask, gateway); + */ + + if(routeparts.length==5) { + assert(routeparts[3].equals("dev")); + mOpenVPNService.addRoute(routeparts[0], routeparts[1], routeparts[2], routeparts[4]); + } else if (routeparts.length >= 3) { + mOpenVPNService.addRoute(routeparts[0], routeparts[1], routeparts[2], null); + } else { + VpnStatus.logError("Unrecognized ROUTE cmd:" + Arrays.toString(routeparts) + " | " + argument); + } + + } else if (needed.equals("ROUTE6")) { + String[] routeparts = extra.split(" "); + mOpenVPNService.addRoutev6(routeparts[0],routeparts[1]); + } else if (needed.equals("IFCONFIG")) { + String[] ifconfigparts = extra.split(" "); + int mtu = Integer.parseInt(ifconfigparts[2]); + mOpenVPNService.setLocalIP(ifconfigparts[0], ifconfigparts[1],mtu,ifconfigparts[3]); + } else if (needed.equals("IFCONFIG6")) { + mOpenVPNService.setLocalIPv6(extra); + + } else if (needed.equals("PERSIST_TUN_ACTION")) { + // check if tun cfg stayed the same + status = mOpenVPNService.getTunReopenStatus(); + } else if (needed.equals("OPENTUN")) { + if(sendTunFD(needed,extra)) + return; + else + status="cancel"; + // This not nice or anything but setFileDescriptors accepts only FilDescriptor class :( + + } else { + Log.e(TAG,"Unkown needok command " + argument); + return; + } + + String cmd = String.format("needok '%s' %s\n", needed, status); + managmentCommand(cmd); + } + + private boolean sendTunFD (String needed, String extra) { + Exception exp; + if(!extra.equals("tun")) { + // We only support tun + VpnStatus.logError(String.format("Device type %s requested, but only tun is possible with the Android API, sorry!",extra)); + + return false; + } + ParcelFileDescriptor pfd = mOpenVPNService.openTun(); + if(pfd==null) + return false; + + Method setInt; + int fdint = pfd.getFd(); + try { + setInt = FileDescriptor.class.getDeclaredMethod("setInt$",int.class); + FileDescriptor fdtosend = new FileDescriptor(); + + setInt.invoke(fdtosend,fdint); + + FileDescriptor[] fds = {fdtosend}; + mSocket.setFileDescriptorsForSend(fds); + + // Trigger a send so we can close the fd on our side of the channel + // The API documentation fails to mention that it will not reset the file descriptor to + // be send and will happily send the file descriptor on every write ... + String cmd = String.format("needok '%s' %s\n", needed, "ok"); + managmentCommand(cmd); + + // Set the FileDescriptor to null to stop this mad behavior + mSocket.setFileDescriptorsForSend(null); + + pfd.close(); + + return true; + } catch (NoSuchMethodException e) { + exp =e; + } catch (IllegalArgumentException e) { + exp =e; + } catch (IllegalAccessException e) { + exp =e; + } catch (InvocationTargetException e) { + exp =e; + } catch (IOException e) { + exp =e; + } + VpnStatus.logException("Could not send fd over socket" , exp); + + return false; + } + + private void processPWCommand(String argument) { + //argument has the form Need 'Private Key' password + // or ">PASSWORD:Verification Failed: '%s' ['%s']" + String needed; + + + + try{ + + int p1 = argument.indexOf('\''); + int p2 = argument.indexOf('\'',p1+1); + needed = argument.substring(p1+1, p2); + if (argument.startsWith("Verification Failed")) { + proccessPWFailed(needed, argument.substring(p2+1)); + return; + } + } catch (StringIndexOutOfBoundsException sioob) { + VpnStatus.logError("Could not parse management Password command: " + argument); + return; + } + + String pw=null; + + if(needed.equals("Private Key")) { + pw = mProfile.getPasswordPrivateKey(); + } else if (needed.equals("Auth")) { + String usercmd = String.format("username '%s' %s\n", + needed, VpnProfile.openVpnEscape(mProfile.mUsername)); + managmentCommand(usercmd); + pw = mProfile.getPasswordAuth(); + } + if(pw!=null) { + String cmd = String.format("password '%s' %s\n", needed, VpnProfile.openVpnEscape(pw)); + managmentCommand(cmd); + } else { + VpnStatus.logError(String.format("Openvpn requires Authentication type '%s' but no password/key information available", needed)); + } + + } + + + + + private void proccessPWFailed(String needed, String args) { + VpnStatus.updateStateString("AUTH_FAILED", needed + args, R.string.state_auth_failed, ConnectionStatus.LEVEL_AUTH_FAILED); + } + + + private static boolean stopOpenVPN() { + boolean sendCMD=false; + for (OpenVpnManagementThread mt: active){ + mt.managmentCommand("signal SIGINT\n"); + sendCMD=true; + try { + if(mt.mSocket !=null) + mt.mSocket.close(); + } catch (IOException e) { + // Ignore close error on already closed socket + } + } + return sendCMD; + } + + @Override + public void networkChange() { + if(!mWaitingForRelease) + managmentCommand("network-change\n"); + } + + public void signalusr1() { + mReleaseHold=false; + + if(!mWaitingForRelease) + managmentCommand("signal SIGUSR1\n"); + else + // If signalusr1 is called update the state string + // if there is another for stopping + VpnStatus.updateStatePause(lastPauseReason); + } + + public void reconnect() { + signalusr1(); + releaseHold(); + } + + private void processSignCommand(String b64data) { + + String signed_string = mProfile.getSignedData(b64data); + if(signed_string==null) { + managmentCommand("rsa-sig\n"); + managmentCommand("\nEND\n"); + stopOpenVPN(); + return; + } + managmentCommand("rsa-sig\n"); + managmentCommand(signed_string); + managmentCommand("\nEND\n"); + } + + @Override + public void pause (pauseReason reason) { + lastPauseReason = reason; + signalusr1(); + } + + @Override + public void resume() { + releaseHold(); + /* Reset the reason why we are disconnected */ + lastPauseReason = pauseReason.noNetwork; + } + + @Override + public boolean stopVPN() { + return stopOpenVPN(); + } +} 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 b1f9dbd4..8281aed8 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVpnService.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVpnService.java @@ -30,8 +30,8 @@ import java.util.HashMap; import java.util.Locale; import java.util.Vector; -import de.blinkt.openvpn.BuildConfig; -import de.blinkt.openvpn.R; +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; @@ -50,7 +50,7 @@ public class OpenVpnService extends VpnService implements StateListener, Callbac public static final String ALWAYS_SHOW_NOTIFICATION = "de.blinkt.openvpn.NOTIFICATION_ALWAYS_VISIBLE"; public static final String DISCONNECT_VPN = "de.blinkt.openvpn.DISCONNECT_VPN"; private static final String PAUSE_VPN = "de.blinkt.openvpn.PAUSE_VPN"; - private static final String RESUME_VPN = "de.blinkt.openvpn.RESUME_VPN"; + private static final String RESUME_VPN = "se.leap.bitmaskclient.RESUME_VPN"; private static final int OPENVPN_STATUS = 1; private static boolean mNotificationAlwaysVisible = false; private final Vector mDnslist = new Vector(); diff --git a/app/src/main/java/de/blinkt/openvpn/core/ProxyDetection.java b/app/src/main/java/de/blinkt/openvpn/core/ProxyDetection.java index 4f66c503..47d88279 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/ProxyDetection.java +++ b/app/src/main/java/de/blinkt/openvpn/core/ProxyDetection.java @@ -9,7 +9,7 @@ import java.net.URISyntaxException; import java.net.URL; import java.util.List; -import de.blinkt.openvpn.R; +import se.leap.bitmaskclient.R; import de.blinkt.openvpn.VpnProfile; public class ProxyDetection { diff --git a/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java b/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java index 5f1efb5f..55fcb0ba 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java +++ b/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java @@ -8,7 +8,7 @@ import java.io.InputStream; import android.content.Context; import android.content.Intent; import android.os.Build; -import de.blinkt.openvpn.R; +import se.leap.bitmaskclient.R; import de.blinkt.openvpn.VpnProfile; public class VPNLaunchHelper { diff --git a/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java b/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java index d146aef8..c19daeb0 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java +++ b/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java @@ -9,7 +9,7 @@ import android.content.pm.Signature; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; -import de.blinkt.openvpn.R; +import se.leap.bitmaskclient.R; import java.io.ByteArrayInputStream; import java.io.FileNotFoundException; diff --git a/app/src/main/java/de/blinkt/openvpn/core/X509Utils.java b/app/src/main/java/de/blinkt/openvpn/core/X509Utils.java index da1e4ed5..35e53c08 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/X509Utils.java +++ b/app/src/main/java/de/blinkt/openvpn/core/X509Utils.java @@ -3,7 +3,7 @@ package de.blinkt.openvpn.core; import android.content.Context; import android.text.TextUtils; -import de.blinkt.openvpn.R; +import se.leap.bitmaskclient.R; import de.blinkt.openvpn.VpnProfile; import org.spongycastle.util.io.pem.PemObject; import org.spongycastle.util.io.pem.PemReader; diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/AboutFragment.java b/app/src/main/java/de/blinkt/openvpn/fragments/AboutFragment.java index a43bbbe8..f878e4fb 100644 --- a/app/src/main/java/de/blinkt/openvpn/fragments/AboutFragment.java +++ b/app/src/main/java/de/blinkt/openvpn/fragments/AboutFragment.java @@ -22,7 +22,7 @@ import android.view.ViewGroup; import android.webkit.WebView; import android.widget.TextView; import com.android.vending.billing.IInAppBillingService; -import de.blinkt.openvpn.R; +import se.leap.bitmaskclient.R; import de.blinkt.openvpn.core.VpnStatus; import org.json.JSONException; import org.json.JSONObject; diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/FaqFragment.java b/app/src/main/java/de/blinkt/openvpn/fragments/FaqFragment.java index 238ad952..ef57a395 100644 --- a/app/src/main/java/de/blinkt/openvpn/fragments/FaqFragment.java +++ b/app/src/main/java/de/blinkt/openvpn/fragments/FaqFragment.java @@ -8,7 +8,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import de.blinkt.openvpn.R; +import se.leap.bitmaskclient.R; public class FaqFragment extends Fragment { diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/FileSelectionFragment.java b/app/src/main/java/de/blinkt/openvpn/fragments/FileSelectionFragment.java index 84e065a5..271af173 100644 --- a/app/src/main/java/de/blinkt/openvpn/fragments/FileSelectionFragment.java +++ b/app/src/main/java/de/blinkt/openvpn/fragments/FileSelectionFragment.java @@ -20,7 +20,7 @@ import android.widget.ListView; import android.widget.SimpleAdapter; import android.widget.TextView; import de.blinkt.openvpn.activities.FileSelect; -import de.blinkt.openvpn.R; +import se.leap.bitmaskclient.R; public class FileSelectionFragment extends ListFragment { diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/InlineFileTab.java b/app/src/main/java/de/blinkt/openvpn/fragments/InlineFileTab.java index bea22442..f0aa4fe2 100644 --- a/app/src/main/java/de/blinkt/openvpn/fragments/InlineFileTab.java +++ b/app/src/main/java/de/blinkt/openvpn/fragments/InlineFileTab.java @@ -10,7 +10,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.EditText; import de.blinkt.openvpn.activities.FileSelect; -import de.blinkt.openvpn.R; +import se.leap.bitmaskclient.R; public class InlineFileTab extends Fragment { diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/OpenVpnPreferencesFragment.java b/app/src/main/java/de/blinkt/openvpn/fragments/OpenVpnPreferencesFragment.java index f23a50db..4a6f19b5 100644 --- a/app/src/main/java/de/blinkt/openvpn/fragments/OpenVpnPreferencesFragment.java +++ b/app/src/main/java/de/blinkt/openvpn/fragments/OpenVpnPreferencesFragment.java @@ -1,48 +1,48 @@ -package de.blinkt.openvpn.fragments; - -import android.os.Bundle; -import android.preference.PreferenceFragment; -import de.blinkt.openvpn.R; -import de.blinkt.openvpn.VpnProfile; -import de.blinkt.openvpn.core.ProfileManager; - -public abstract class OpenVpnPreferencesFragment extends PreferenceFragment { - - protected VpnProfile mProfile; - - protected abstract void loadSettings(); - protected abstract void saveSettings(); - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - String profileUUID = getArguments().getString(getActivity().getPackageName() + ".profileUUID"); - mProfile = ProfileManager.get(getActivity(),profileUUID); - getActivity().setTitle(getString(R.string.edit_profile_title, mProfile.getName())); - - } - - @Override - public void onPause() { - super.onPause(); - saveSettings(); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - if(savedInstanceState!=null) { - String profileUUID=savedInstanceState.getString(VpnProfile.EXTRA_PROFILEUUID); - mProfile = ProfileManager.get(getActivity(),profileUUID); - loadSettings(); - } - } - - @Override - public void onSaveInstanceState (Bundle outState) { - super.onSaveInstanceState(outState); - saveSettings(); - outState.putString(VpnProfile.EXTRA_PROFILEUUID, mProfile.getUUIDString()); - } -} +package de.blinkt.openvpn.fragments; + +import android.os.Bundle; +import android.preference.PreferenceFragment; +import se.leap.bitmaskclient.R; +import de.blinkt.openvpn.VpnProfile; +import de.blinkt.openvpn.core.ProfileManager; + +public abstract class OpenVpnPreferencesFragment extends PreferenceFragment { + + protected VpnProfile mProfile; + + protected abstract void loadSettings(); + protected abstract void saveSettings(); + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + String profileUUID = getArguments().getString(getActivity().getPackageName() + ".profileUUID"); + mProfile = ProfileManager.get(getActivity(),profileUUID); + getActivity().setTitle(getString(R.string.edit_profile_title, mProfile.getName())); + + } + + @Override + public void onPause() { + super.onPause(); + saveSettings(); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + if(savedInstanceState!=null) { + String profileUUID=savedInstanceState.getString(VpnProfile.EXTRA_PROFILEUUID); + mProfile = ProfileManager.get(getActivity(),profileUUID); + loadSettings(); + } + } + + @Override + public void onSaveInstanceState (Bundle outState) { + super.onSaveInstanceState(outState); + saveSettings(); + outState.putString(VpnProfile.EXTRA_PROFILEUUID, mProfile.getUUIDString()); + } +} diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/SendDumpFragment.java b/app/src/main/java/de/blinkt/openvpn/fragments/SendDumpFragment.java index d43f6427..fd01b89f 100644 --- a/app/src/main/java/de/blinkt/openvpn/fragments/SendDumpFragment.java +++ b/app/src/main/java/de/blinkt/openvpn/fragments/SendDumpFragment.java @@ -14,7 +14,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; -import de.blinkt.openvpn.R; +import se.leap.bitmaskclient.R; import de.blinkt.openvpn.core.VpnStatus; public class SendDumpFragment extends Fragment { diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Authentication.java b/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Authentication.java index 6ce9c915..9bd3bf54 100644 --- a/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Authentication.java +++ b/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Authentication.java @@ -13,7 +13,7 @@ import android.preference.Preference.OnPreferenceClickListener; import android.preference.SwitchPreference; import android.util.Pair; import de.blinkt.openvpn.activities.FileSelect; -import de.blinkt.openvpn.R; +import se.leap.bitmaskclient.R; import de.blinkt.openvpn.core.VpnStatus; import de.blinkt.openvpn.views.RemoteCNPreference; import de.blinkt.openvpn.VpnProfile; diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Basic.java b/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Basic.java index 4145c65f..203d6c5c 100644 --- a/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Basic.java +++ b/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Basic.java @@ -27,9 +27,9 @@ import android.widget.Spinner; import android.widget.TextView; import android.widget.ToggleButton; import de.blinkt.openvpn.views.FileSelectLayout; -import de.blinkt.openvpn.R; +import se.leap.bitmaskclient.R; import de.blinkt.openvpn.VpnProfile; -import de.blinkt.openvpn.R.id; +import se.leap.bitmaskclient.R.id; import de.blinkt.openvpn.core.ProfileManager; import de.blinkt.openvpn.core.X509Utils; diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/Settings_IP.java b/app/src/main/java/de/blinkt/openvpn/fragments/Settings_IP.java index 16e3a5c4..81988012 100644 --- a/app/src/main/java/de/blinkt/openvpn/fragments/Settings_IP.java +++ b/app/src/main/java/de/blinkt/openvpn/fragments/Settings_IP.java @@ -6,7 +6,7 @@ import android.preference.Preference; import android.preference.Preference.OnPreferenceChangeListener; import android.preference.PreferenceManager; import android.preference.SwitchPreference; -import de.blinkt.openvpn.R; +import se.leap.bitmaskclient.R; import de.blinkt.openvpn.VpnProfile; public class Settings_IP extends OpenVpnPreferencesFragment implements OnPreferenceChangeListener { diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Obscure.java b/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Obscure.java index 0e8f1a02..4507d691 100644 --- a/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Obscure.java +++ b/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Obscure.java @@ -6,7 +6,7 @@ import android.preference.EditTextPreference; import android.preference.ListPreference; import android.preference.Preference; import android.preference.Preference.OnPreferenceChangeListener; -import de.blinkt.openvpn.R; +import se.leap.bitmaskclient.R; public class Settings_Obscure extends OpenVpnPreferencesFragment implements OnPreferenceChangeListener { private CheckBoxPreference mUseRandomHostName; diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Routing.java b/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Routing.java index c6f0dcf8..8163a745 100644 --- a/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Routing.java +++ b/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Routing.java @@ -4,7 +4,7 @@ import android.preference.CheckBoxPreference; import android.preference.EditTextPreference; import android.preference.Preference; import android.preference.Preference.OnPreferenceChangeListener; -import de.blinkt.openvpn.R; +import se.leap.bitmaskclient.R; public class Settings_Routing extends OpenVpnPreferencesFragment implements OnPreferenceChangeListener { diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/ShowConfigFragment.java b/app/src/main/java/de/blinkt/openvpn/fragments/ShowConfigFragment.java index bf673288..0cf2bbb7 100644 --- a/app/src/main/java/de/blinkt/openvpn/fragments/ShowConfigFragment.java +++ b/app/src/main/java/de/blinkt/openvpn/fragments/ShowConfigFragment.java @@ -10,7 +10,7 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import de.blinkt.openvpn.R; +import se.leap.bitmaskclient.R; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.ProfileManager; diff --git a/app/src/main/java/de/blinkt/openvpn/views/FileSelectLayout.java b/app/src/main/java/de/blinkt/openvpn/views/FileSelectLayout.java index 665b525f..06dbec22 100644 --- a/app/src/main/java/de/blinkt/openvpn/views/FileSelectLayout.java +++ b/app/src/main/java/de/blinkt/openvpn/views/FileSelectLayout.java @@ -1,6 +1,6 @@ package de.blinkt.openvpn.views; -import de.blinkt.openvpn.R; +import se.leap.bitmaskclient.R; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.activities.FileSelect; import de.blinkt.openvpn.core.VpnStatus; diff --git a/app/src/main/java/de/blinkt/openvpn/views/RemoteCNPreference.java b/app/src/main/java/de/blinkt/openvpn/views/RemoteCNPreference.java index 388f892b..b24e6f8f 100644 --- a/app/src/main/java/de/blinkt/openvpn/views/RemoteCNPreference.java +++ b/app/src/main/java/de/blinkt/openvpn/views/RemoteCNPreference.java @@ -10,7 +10,7 @@ import android.widget.EditText; import android.widget.Spinner; import android.widget.TextView; -import de.blinkt.openvpn.R; +import se.leap.bitmaskclient.R; import de.blinkt.openvpn.VpnProfile; public class RemoteCNPreference extends DialogPreference { -- cgit v1.2.3 From a7f8db4fdb308f2fcd1cc1c5013ac930cc063ec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parm=C3=A9nides=20GV?= Date: Mon, 19 May 2014 16:51:48 +0200 Subject: bitmaskclient.R in de.blinkt.openvpn top level --- app/src/main/java/de/blinkt/openvpn/FileProvider.java | 2 ++ app/src/main/java/de/blinkt/openvpn/LaunchVPN.java | 2 ++ app/src/main/java/de/blinkt/openvpn/OnBootReceiver.java | 2 ++ app/src/main/java/de/blinkt/openvpn/VpnProfile.java | 2 ++ 4 files changed, 8 insertions(+) (limited to 'app/src/main/java/de/blinkt/openvpn') diff --git a/app/src/main/java/de/blinkt/openvpn/FileProvider.java b/app/src/main/java/de/blinkt/openvpn/FileProvider.java index 9ffd6545..f9fedd02 100644 --- a/app/src/main/java/de/blinkt/openvpn/FileProvider.java +++ b/app/src/main/java/de/blinkt/openvpn/FileProvider.java @@ -1,5 +1,7 @@ package de.blinkt.openvpn; +import se.leap.bitmaskclient.R; + import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; diff --git a/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java b/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java index 7aed4e4a..cfd0efb0 100644 --- a/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java +++ b/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java @@ -1,5 +1,7 @@ package de.blinkt.openvpn; +import se.leap.bitmaskclient.R; + import java.io.IOException; import android.app.Activity; diff --git a/app/src/main/java/de/blinkt/openvpn/OnBootReceiver.java b/app/src/main/java/de/blinkt/openvpn/OnBootReceiver.java index 32d14b1b..129d8d1c 100644 --- a/app/src/main/java/de/blinkt/openvpn/OnBootReceiver.java +++ b/app/src/main/java/de/blinkt/openvpn/OnBootReceiver.java @@ -1,5 +1,7 @@ package de.blinkt.openvpn; +import se.leap.bitmaskclient.R; + import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; diff --git a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java index 601fb2df..afa70100 100644 --- a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java +++ b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java @@ -1,5 +1,7 @@ package de.blinkt.openvpn; +import se.leap.bitmaskclient.R; + import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -- cgit v1.2.3 From baad99940a0bfa2e4c27a9cbc0f478e94b6d20ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parm=C3=A9nides=20GV?= Date: Mon, 19 May 2014 17:44:25 +0200 Subject: Copy necessary activities and fragments. Now we need to decide what's our relationship with LogWindow and LaunchVPN, refactor its classes and fix ours so that we use the currently supported methods. --- .../blinkt/openvpn/activities/ConfigConverter.java | 639 --------------------- .../blinkt/openvpn/activities/CreateShortcuts.java | 154 ----- .../de/blinkt/openvpn/activities/FileSelect.java | 220 ------- .../de/blinkt/openvpn/activities/MainActivity.java | 103 ---- .../blinkt/openvpn/activities/VPNPreferences.java | 165 ------ .../de/blinkt/openvpn/fragments/AboutFragment.java | 297 ---------- .../de/blinkt/openvpn/fragments/FaqFragment.java | 36 -- .../openvpn/fragments/FileSelectionFragment.java | 256 --------- .../de/blinkt/openvpn/fragments/InlineFileTab.java | 66 --- .../de/blinkt/openvpn/fragments/LogFragment.java | 2 + .../fragments/OpenVpnPreferencesFragment.java | 48 -- .../blinkt/openvpn/fragments/SendDumpFragment.java | 94 --- .../openvpn/fragments/Settings_Authentication.java | 214 ------- .../blinkt/openvpn/fragments/Settings_Basic.java | 360 ------------ .../de/blinkt/openvpn/fragments/Settings_IP.java | 130 ----- .../blinkt/openvpn/fragments/Settings_Obscure.java | 93 --- .../blinkt/openvpn/fragments/Settings_Routing.java | 88 --- .../openvpn/fragments/ShowConfigFragment.java | 89 --- .../java/de/blinkt/openvpn/fragments/Utils.java | 221 ------- .../blinkt/openvpn/fragments/VPNProfileList.java | 346 ----------- .../de/blinkt/openvpn/views/FileSelectLayout.java | 158 ----- .../blinkt/openvpn/views/RemoteCNPreference.java | 141 ----- .../java/de/blinkt/openvpn/views/SeekBarTicks.java | 69 --- 23 files changed, 2 insertions(+), 3987 deletions(-) delete mode 100644 app/src/main/java/de/blinkt/openvpn/activities/ConfigConverter.java delete mode 100644 app/src/main/java/de/blinkt/openvpn/activities/CreateShortcuts.java delete mode 100644 app/src/main/java/de/blinkt/openvpn/activities/FileSelect.java delete mode 100644 app/src/main/java/de/blinkt/openvpn/activities/MainActivity.java delete mode 100644 app/src/main/java/de/blinkt/openvpn/activities/VPNPreferences.java delete mode 100644 app/src/main/java/de/blinkt/openvpn/fragments/AboutFragment.java delete mode 100644 app/src/main/java/de/blinkt/openvpn/fragments/FaqFragment.java delete mode 100644 app/src/main/java/de/blinkt/openvpn/fragments/FileSelectionFragment.java delete mode 100644 app/src/main/java/de/blinkt/openvpn/fragments/InlineFileTab.java delete mode 100644 app/src/main/java/de/blinkt/openvpn/fragments/OpenVpnPreferencesFragment.java delete mode 100644 app/src/main/java/de/blinkt/openvpn/fragments/SendDumpFragment.java delete mode 100644 app/src/main/java/de/blinkt/openvpn/fragments/Settings_Authentication.java delete mode 100644 app/src/main/java/de/blinkt/openvpn/fragments/Settings_Basic.java delete mode 100644 app/src/main/java/de/blinkt/openvpn/fragments/Settings_IP.java delete mode 100644 app/src/main/java/de/blinkt/openvpn/fragments/Settings_Obscure.java delete mode 100644 app/src/main/java/de/blinkt/openvpn/fragments/Settings_Routing.java delete mode 100644 app/src/main/java/de/blinkt/openvpn/fragments/ShowConfigFragment.java delete mode 100644 app/src/main/java/de/blinkt/openvpn/fragments/Utils.java delete mode 100644 app/src/main/java/de/blinkt/openvpn/fragments/VPNProfileList.java delete mode 100644 app/src/main/java/de/blinkt/openvpn/views/FileSelectLayout.java delete mode 100644 app/src/main/java/de/blinkt/openvpn/views/RemoteCNPreference.java delete mode 100644 app/src/main/java/de/blinkt/openvpn/views/SeekBarTicks.java (limited to 'app/src/main/java/de/blinkt/openvpn') diff --git a/app/src/main/java/de/blinkt/openvpn/activities/ConfigConverter.java b/app/src/main/java/de/blinkt/openvpn/activities/ConfigConverter.java deleted file mode 100644 index 2cb79e05..00000000 --- a/app/src/main/java/de/blinkt/openvpn/activities/ConfigConverter.java +++ /dev/null @@ -1,639 +0,0 @@ - -package de.blinkt.openvpn.activities; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.AlertDialog.Builder; -import android.content.ActivityNotFoundException; -import android.content.Intent; -import android.database.Cursor; -import android.os.Bundle; -import android.os.Environment; -import android.provider.OpenableColumns; -import android.security.KeyChain; -import android.security.KeyChainAliasCallback; -import android.text.TextUtils; -import android.util.Base64; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CheckBox; -import android.widget.LinearLayout; -import android.widget.TextView; - -import junit.framework.Assert; - -import org.jetbrains.annotations.NotNull; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Vector; - -import se.leap.bitmaskclient.R; -import de.blinkt.openvpn.VpnProfile; -import de.blinkt.openvpn.core.ConfigParser; -import de.blinkt.openvpn.core.ConfigParser.ConfigParseError; -import de.blinkt.openvpn.core.ProfileManager; -import de.blinkt.openvpn.fragments.Utils; -import de.blinkt.openvpn.views.FileSelectLayout; - -import static de.blinkt.openvpn.views.FileSelectLayout.FileSelectCallback; - -public class ConfigConverter extends Activity implements FileSelectCallback { - - public static final String IMPORT_PROFILE = "de.blinkt.openvpn.IMPORT_PROFILE"; - private static final int RESULT_INSTALLPKCS12 = 7; - private static final int CHOOSE_FILE_OFFSET = 1000; - public static final String VPNPROFILE = "vpnProfile"; - - private VpnProfile mResult; - - private transient List mPathsegments; - - private String mAliasName = null; - - - private Map fileSelectMap = new HashMap(); - private String mEmbeddedPwFile; - private Vector mLogEntries = new Vector(); - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == R.id.cancel) { - setResult(Activity.RESULT_CANCELED); - finish(); - } else if (item.getItemId() == R.id.ok) { - if (mResult == null) { - log("Importing the config had error, cannot save it"); - return true; - } - - Intent in = installPKCS12(); - - if (in != null) - startActivityForResult(in, RESULT_INSTALLPKCS12); - else - saveProfile(); - - return true; - } - - return super.onOptionsItemSelected(item); - - } - - @Override - protected void onSaveInstanceState(@NotNull Bundle outState) { - super.onSaveInstanceState(outState); - if (mResult != null) - outState.putSerializable(VPNPROFILE, mResult); - outState.putString("mAliasName", mAliasName); - - - - String[] logentries = mLogEntries.toArray(new String[mLogEntries.size()]); - - outState.putStringArray("logentries", logentries); - - int[] fileselects = new int[fileSelectMap.size()]; - int k = 0; - for (Utils.FileType key : fileSelectMap.keySet()) { - fileselects[k] = key.getValue(); - k++; - } - outState.putIntArray("fileselects", fileselects); - outState.putString("pwfile",mEmbeddedPwFile); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent result) { - if (requestCode == RESULT_INSTALLPKCS12 && resultCode == Activity.RESULT_OK) { - showCertDialog(); - } - - if (resultCode == Activity.RESULT_OK && requestCode >= CHOOSE_FILE_OFFSET) { - Utils.FileType type = Utils.FileType.getFileTypeByValue(requestCode - CHOOSE_FILE_OFFSET); - - - FileSelectLayout fs = fileSelectMap.get(type); - fs.parseResponse(result, this); - - String data = fs.getData(); - - switch (type) { - case USERPW_FILE: - mEmbeddedPwFile = data; - break; - case PKCS12: - mResult.mPKCS12Filename = data; - break; - case TLS_AUTH_FILE: - mResult.mTLSAuthFilename = data; - break; - case CA_CERTIFICATE: - mResult.mCaFilename = data; - break; - case CLIENT_CERTIFICATE: - mResult.mClientCertFilename = data; - break; - case KEYFILE: - mResult.mClientKeyFilename = data; - break; - default: - Assert.fail(); - } - } - - super.onActivityResult(requestCode, resultCode, result); - } - - private void saveProfile() { - Intent result = new Intent(); - ProfileManager vpl = ProfileManager.getInstance(this); - - if (!TextUtils.isEmpty(mEmbeddedPwFile)) - ConfigParser.useEmbbedUserAuth(mResult, mEmbeddedPwFile); - - vpl.addProfile(mResult); - vpl.saveProfile(this, mResult); - vpl.saveProfileList(this); - result.putExtra(VpnProfile.EXTRA_PROFILEUUID, mResult.getUUID().toString()); - setResult(Activity.RESULT_OK, result); - finish(); - } - - public void showCertDialog() { - try { - KeyChain.choosePrivateKeyAlias(this, - new KeyChainAliasCallback() { - - public void alias(String alias) { - // Credential alias selected. Remember the alias selection for future use. - mResult.mAlias = alias; - saveProfile(); - } - - - }, - new String[]{"RSA"}, // List of acceptable key types. null for any - null, // issuer, null for any - mResult.mServerName, // host name of server requesting the cert, null if unavailable - -1, // port of server requesting the cert, -1 if unavailable - mAliasName); // alias to preselect, null if unavailable - } catch (ActivityNotFoundException anf) { - Builder ab = new AlertDialog.Builder(this); - ab.setTitle(R.string.broken_image_cert_title); - ab.setMessage(R.string.broken_image_cert); - ab.setPositiveButton(android.R.string.ok, null); - ab.show(); - } - } - - - private Intent installPKCS12() { - - if (!((CheckBox) findViewById(R.id.importpkcs12)).isChecked()) { - setAuthTypeToEmbeddedPKCS12(); - return null; - - } - String pkcs12datastr = mResult.mPKCS12Filename; - if (VpnProfile.isEmbedded(pkcs12datastr)) { - Intent inkeyIntent = KeyChain.createInstallIntent(); - - pkcs12datastr = VpnProfile.getEmbeddedContent(pkcs12datastr); - - - byte[] pkcs12data = Base64.decode(pkcs12datastr, Base64.DEFAULT); - - - inkeyIntent.putExtra(KeyChain.EXTRA_PKCS12, pkcs12data); - - if (mAliasName.equals("")) - mAliasName = null; - - if (mAliasName != null) { - inkeyIntent.putExtra(KeyChain.EXTRA_NAME, mAliasName); - } - return inkeyIntent; - - } - return null; - } - - - private void setAuthTypeToEmbeddedPKCS12() { - if (VpnProfile.isEmbedded(mResult.mPKCS12Filename)) { - if (mResult.mAuthenticationType == VpnProfile.TYPE_USERPASS_KEYSTORE) - mResult.mAuthenticationType = VpnProfile.TYPE_USERPASS_PKCS12; - - if (mResult.mAuthenticationType == VpnProfile.TYPE_KEYSTORE) - mResult.mAuthenticationType = VpnProfile.TYPE_PKCS12; - - } - } - - - private String getUniqueProfileName(String possibleName) { - - int i = 0; - - ProfileManager vpl = ProfileManager.getInstance(this); - - String newname = possibleName; - - // Default to - if (mResult.mName != null && !ConfigParser.CONVERTED_PROFILE.equals(mResult.mName)) - newname = mResult.mName; - - while (newname == null || vpl.getProfileByName(newname) != null) { - i++; - if (i == 1) - newname = getString(R.string.converted_profile); - else - newname = getString(R.string.converted_profile_i, i); - } - - return newname; - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.import_menu, menu); - return true; - } - - private String embedFile(String filename, Utils.FileType type) { - if (filename == null) - return null; - - // Already embedded, nothing to do - if (VpnProfile.isEmbedded(filename)) - return filename; - - File possibleFile = findFile(filename, type); - if (possibleFile == null) - return filename; - else - return readFileContent(possibleFile, type == Utils.FileType.PKCS12); - - } - - private File findFile(String filename, Utils.FileType fileType) { - File foundfile = findFileRaw(filename); - - if (foundfile == null && filename != null && !filename.equals("")) { - log(R.string.import_could_not_open, filename); - addFileSelectDialog(fileType); - } - - - return foundfile; - } - - private void addFileSelectDialog(Utils.FileType type) { - int titleRes = 0; - String value = null; - switch (type) { - case KEYFILE: - titleRes = R.string.client_key_title; - if (mResult != null) - value = mResult.mClientKeyFilename; - break; - case CLIENT_CERTIFICATE: - titleRes = R.string.client_certificate_title; - if (mResult != null) - value = mResult.mClientCertFilename; - break; - case CA_CERTIFICATE: - titleRes = R.string.ca_title; - if (mResult != null) - value = mResult.mCaFilename; - break; - case TLS_AUTH_FILE: - titleRes = R.string.tls_auth_file; - if (mResult != null) - value = mResult.mTLSAuthFilename; - break; - case PKCS12: - titleRes = R.string.client_pkcs12_title; - if (mResult != null) - value = mResult.mPKCS12Filename; - break; - - case USERPW_FILE: - titleRes = R.string.userpw_file; - value = mEmbeddedPwFile; - break; - - } - - boolean isCert = type == Utils.FileType.CA_CERTIFICATE || type == Utils.FileType.CLIENT_CERTIFICATE; - FileSelectLayout fl = new FileSelectLayout(this, getString(titleRes), isCert); - fileSelectMap.put(type, fl); - fl.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - - ((LinearLayout) findViewById(R.id.config_convert_root)).addView(fl, 2); - findViewById(R.id.files_missing_hint).setVisibility(View.VISIBLE); - fl.setData(value, this); - int i = getFileLayoutOffset(type); - fl.setCaller(this, i, type); - - } - - private int getFileLayoutOffset(Utils.FileType type) { - return CHOOSE_FILE_OFFSET + type.getValue(); - } - - - private File findFileRaw(String filename) { - if (filename == null || filename.equals("")) - return null; - - // Try diffent path relative to /mnt/sdcard - File sdcard = Environment.getExternalStorageDirectory(); - File root = new File("/"); - - HashSet dirlist = new HashSet(); - - for (int i = mPathsegments.size() - 1; i >= 0; i--) { - String path = ""; - for (int j = 0; j <= i; j++) { - path += "/" + mPathsegments.get(j); - } - // Do a little hackish dance for the Android File Importer - // /document/primary:ovpn/openvpn-imt.conf - - - if (path.indexOf(':') != -1 && path.indexOf('/') > path.indexOf(':')) { - String possibleDir = path.substring(path.indexOf(':') + 1, path.length()); - possibleDir = possibleDir.substring(0, possibleDir.lastIndexOf('/')); - - - dirlist.add(new File(sdcard, possibleDir)); - - } - dirlist.add(new File(path)); - - - } - dirlist.add(sdcard); - dirlist.add(root); - - - String[] fileparts = filename.split("/"); - for (File rootdir : dirlist) { - String suffix = ""; - for (int i = fileparts.length - 1; i >= 0; i--) { - if (i == fileparts.length - 1) - suffix = fileparts[i]; - else - suffix = fileparts[i] + "/" + suffix; - - File possibleFile = new File(rootdir, suffix); - if (!possibleFile.canRead()) - continue; - - // read the file inline - return possibleFile; - - } - } - return null; - } - - String readFileContent(File possibleFile, boolean base64encode) { - byte[] filedata; - try { - filedata = readBytesFromFile(possibleFile); - } catch (IOException e) { - log(e.getLocalizedMessage()); - return null; - } - - String data; - if (base64encode) { - data = Base64.encodeToString(filedata, Base64.DEFAULT); - } else { - data = new String(filedata); - - } - - return VpnProfile.DISPLAYNAME_TAG + possibleFile.getName() + VpnProfile.INLINE_TAG + data; - - } - - - private byte[] readBytesFromFile(File file) throws IOException { - InputStream input = new FileInputStream(file); - - long len = file.length(); - if (len > VpnProfile.MAX_EMBED_FILE_SIZE) - throw new IOException("File size of file to import too large."); - - // Create the byte array to hold the data - byte[] bytes = new byte[(int) len]; - - // Read in the bytes - int offset = 0; - int bytesRead; - while (offset < bytes.length - && (bytesRead = input.read(bytes, offset, bytes.length - offset)) >= 0) { - offset += bytesRead; - } - - input.close(); - return bytes; - } - - void embedFiles() { - // This where I would like to have a c++ style - // void embedFile(std::string & option) - - if (mResult.mPKCS12Filename != null) { - File pkcs12file = findFileRaw(mResult.mPKCS12Filename); - if (pkcs12file != null) { - mAliasName = pkcs12file.getName().replace(".p12", ""); - } else { - mAliasName = "Imported PKCS12"; - } - } - - - mResult.mCaFilename = embedFile(mResult.mCaFilename, Utils.FileType.CA_CERTIFICATE); - mResult.mClientCertFilename = embedFile(mResult.mClientCertFilename, Utils.FileType.CLIENT_CERTIFICATE); - mResult.mClientKeyFilename = embedFile(mResult.mClientKeyFilename, Utils.FileType.KEYFILE); - mResult.mTLSAuthFilename = embedFile(mResult.mTLSAuthFilename, Utils.FileType.TLS_AUTH_FILE); - mResult.mPKCS12Filename = embedFile(mResult.mPKCS12Filename, Utils.FileType.PKCS12); - mEmbeddedPwFile = embedFile(mResult.mPassword, Utils.FileType.USERPW_FILE); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - - setContentView(R.layout.config_converter); - super.onCreate(savedInstanceState); - - if (savedInstanceState != null && savedInstanceState.containsKey(VPNPROFILE)) { - mResult = (VpnProfile) savedInstanceState.getSerializable(VPNPROFILE); - mAliasName = savedInstanceState.getString("mAliasName"); - mEmbeddedPwFile = savedInstanceState.getString("pwfile"); - - if (savedInstanceState.containsKey("logentries")) { - //noinspection ConstantConditions - for (String logItem : savedInstanceState.getStringArray("logentries")) - log(logItem); - } - if (savedInstanceState.containsKey("fileselects")) { - //noinspection ConstantConditions - for (int k : savedInstanceState.getIntArray("fileselects")) { - addFileSelectDialog(Utils.FileType.getFileTypeByValue(k)); - } - } - return; - } - - - final android.content.Intent intent = getIntent(); - - if (intent != null) { - final android.net.Uri data = intent.getData(); - if (data != null) { - //log(R.string.import_experimental); - log(R.string.importing_config, data.toString()); - try { - String possibleName = null; - if ((data.getScheme() != null && data.getScheme().equals("file")) || - (data.getLastPathSegment() != null && - (data.getLastPathSegment().endsWith(".ovpn") || - data.getLastPathSegment().endsWith(".conf"))) - ) { - possibleName = data.getLastPathSegment(); - if (possibleName.lastIndexOf('/') != -1) - possibleName = possibleName.substring(possibleName.lastIndexOf('/') + 1); - - } - InputStream is = getContentResolver().openInputStream(data); - mPathsegments = data.getPathSegments(); - - Cursor cursor = null; - if (data!=null) - cursor = getContentResolver().query(data, null, null, null, null); - - - try { - - - if (cursor!=null && cursor.moveToFirst()) { - int columnIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); - - if (columnIndex != -1) { - String displayName = cursor.getString(columnIndex); - if (displayName != null) - possibleName = displayName; - } - columnIndex = cursor.getColumnIndex("mime_type"); - if (columnIndex != -1) { - log("Opening Mime TYPE: " + cursor.getString(columnIndex)); - } - } - } finally { - if(cursor!=null) - cursor.close(); - } - if (possibleName != null) { - possibleName = possibleName.replace(".ovpn", ""); - possibleName = possibleName.replace(".conf", ""); - } - - doImport(is, possibleName); - - } catch (FileNotFoundException e) { - log(R.string.import_content_resolve_error); - } - } - - // We parsed the intent, relay on saved instance for restoring - setIntent(null); - } - - - } - - - @Override - protected void onStart() { - super.onStart(); - - - } - - private void log(String logmessage) { - mLogEntries.add(logmessage); - TextView tv = new TextView(this); - tv.setText(logmessage); - LinearLayout logLayout = (LinearLayout) findViewById(R.id.config_convert_root); - logLayout.addView(tv); - } - - private void doImport(InputStream is, String newName) { - ConfigParser cp = new ConfigParser(); - try { - InputStreamReader isr = new InputStreamReader(is); - - cp.parseConfig(isr); - mResult = cp.convertProfile(); - embedFiles(); - displayWarnings(); - mResult.mName = getUniqueProfileName(newName); - - log(R.string.import_done); - return; - - } catch (IOException e) { - log(R.string.error_reading_config_file); - log(e.getLocalizedMessage()); - } catch (ConfigParseError e) { - log(R.string.error_reading_config_file); - log(e.getLocalizedMessage()); - } - mResult = null; - - } - - private void displayWarnings() { - if (mResult.mUseCustomConfig) { - log(R.string.import_warning_custom_options); - String copt = mResult.mCustomConfigOptions; - if (copt.startsWith("#")) { - int until = copt.indexOf('\n'); - copt = copt.substring(until + 1); - } - - log(copt); - } - - if (mResult.mAuthenticationType == VpnProfile.TYPE_KEYSTORE || - mResult.mAuthenticationType == VpnProfile.TYPE_USERPASS_KEYSTORE) { - findViewById(R.id.importpkcs12).setVisibility(View.VISIBLE); - } - - } - - private void log(int ressourceId, Object... formatArgs) { - log(getString(ressourceId, formatArgs)); - } - - -} diff --git a/app/src/main/java/de/blinkt/openvpn/activities/CreateShortcuts.java b/app/src/main/java/de/blinkt/openvpn/activities/CreateShortcuts.java deleted file mode 100644 index 590b7150..00000000 --- a/app/src/main/java/de/blinkt/openvpn/activities/CreateShortcuts.java +++ /dev/null @@ -1,154 +0,0 @@ -package de.blinkt.openvpn.activities; - -import android.app.ListActivity; -import android.content.Intent; -import android.os.Bundle; -import android.os.Parcelable; -import android.view.View; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.ArrayAdapter; -import android.widget.ListView; -import android.widget.TextView; -import de.blinkt.openvpn.LaunchVPN; -import se.leap.bitmaskclient.R; -import de.blinkt.openvpn.VpnProfile; -import de.blinkt.openvpn.core.ProfileManager; - -import java.util.Collection; -import java.util.Vector; - -/** - * This Activity actually handles two stages of a launcher shortcut's life cycle. - * - * 1. Your application offers to provide shortcuts to the launcher. When - * the user installs a shortcut, an activity within your application - * generates the actual shortcut and returns it to the launcher, where it - * is shown to the user as an icon. - * - * 2. Any time the user clicks on an installed shortcut, an intent is sent. - * Typically this would then be handled as necessary by an activity within - * your application. - * - * We handle stage 1 (creating a shortcut) by simply sending back the information (in the form - * of an {@link android.content.Intent} that the launcher will use to create the shortcut. - * - * You can also implement this in an interactive way, by having your activity actually present - * UI for the user to select the specific nature of the shortcut, such as a contact, picture, URL, - * media item, or action. - * - * We handle stage 2 (responding to a shortcut) in this sample by simply displaying the contents - * of the incoming {@link android.content.Intent}. - * - * In a real application, you would probably use the shortcut intent to display specific content - * or start a particular operation. - */ -public class CreateShortcuts extends ListActivity implements OnItemClickListener { - - - private static final int START_VPN_PROFILE= 70; - - - private ProfileManager mPM; - private VpnProfile mSelectedProfile; - - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - - mPM =ProfileManager.getInstance(this); - - } - - @Override - protected void onStart() { - super.onStart(); - // Resolve the intent - - createListView(); - } - - private void createListView() { - ListView lv = getListView(); - //lv.setTextFilterEnabled(true); - - Collection vpnList = mPM.getProfiles(); - - Vector vpnNames=new Vector(); - for (VpnProfile vpnProfile : vpnList) { - vpnNames.add(vpnProfile.mName); - } - - - - ArrayAdapter adapter = new ArrayAdapter(this,android.R.layout.simple_list_item_1,vpnNames); - lv.setAdapter(adapter); - - lv.setOnItemClickListener(this); - } - - /** - * This function creates a shortcut and returns it to the caller. There are actually two - * intents that you will send back. - * - * The first intent serves as a container for the shortcut and is returned to the launcher by - * setResult(). This intent must contain three fields: - * - *
    - *
  • {@link android.content.Intent#EXTRA_SHORTCUT_INTENT} The shortcut intent.
  • - *
  • {@link android.content.Intent#EXTRA_SHORTCUT_NAME} The text that will be displayed with - * the shortcut.
  • - *
  • {@link android.content.Intent#EXTRA_SHORTCUT_ICON} The shortcut's icon, if provided as a - * bitmap, or {@link android.content.Intent#EXTRA_SHORTCUT_ICON_RESOURCE} if provided as - * a drawable resource.
  • - *
- * - * If you use a simple drawable resource, note that you must wrapper it using - * {@link android.content.Intent.ShortcutIconResource}, as shown below. This is required so - * that the launcher can access resources that are stored in your application's .apk file. If - * you return a bitmap, such as a thumbnail, you can simply put the bitmap into the extras - * bundle using {@link android.content.Intent#EXTRA_SHORTCUT_ICON}. - * - * The shortcut intent can be any intent that you wish the launcher to send, when the user - * clicks on the shortcut. Typically this will be {@link android.content.Intent#ACTION_VIEW} - * with an appropriate Uri for your content, but any Intent will work here as long as it - * triggers the desired action within your Activity. - * @param profile - */ - private void setupShortcut(VpnProfile profile) { - // First, set up the shortcut intent. For this example, we simply create an intent that - // will bring us directly back to this activity. A more typical implementation would use a - // data Uri in order to display a more specific result, or a custom action in order to - // launch a specific operation. - - Intent shortcutIntent = new Intent(Intent.ACTION_MAIN); - shortcutIntent.setClass(this, LaunchVPN.class); - shortcutIntent.putExtra(LaunchVPN.EXTRA_KEY,profile.getUUID().toString()); - - // Then, set up the container intent (the response to the caller) - - Intent intent = new Intent(); - intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); - intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, profile.getName()); - Parcelable iconResource = Intent.ShortcutIconResource.fromContext( - this, R.drawable.icon); - intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource); - - // Now, return the result to the launcher - - setResult(RESULT_OK, intent); - } - - - @Override - public void onItemClick(AdapterView parent, View view, int position, - long id) { - String profileName = ((TextView) view).getText().toString(); - - VpnProfile profile = mPM.getProfileByName(profileName); - - setupShortcut(profile); - finish(); - } - -} diff --git a/app/src/main/java/de/blinkt/openvpn/activities/FileSelect.java b/app/src/main/java/de/blinkt/openvpn/activities/FileSelect.java deleted file mode 100644 index a30b694c..00000000 --- a/app/src/main/java/de/blinkt/openvpn/activities/FileSelect.java +++ /dev/null @@ -1,220 +0,0 @@ -package de.blinkt.openvpn.activities; - - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; - -import android.app.ActionBar; -import android.app.ActionBar.Tab; -import android.app.Activity; -import android.app.AlertDialog; -import android.app.AlertDialog.Builder; -import android.app.Fragment; -import android.app.FragmentTransaction; -import android.content.Intent; -import android.os.Bundle; -import android.os.Environment; -import android.util.Base64; - -import se.leap.bitmaskclient.R; -import de.blinkt.openvpn.VpnProfile; -import de.blinkt.openvpn.fragments.FileSelectionFragment; -import de.blinkt.openvpn.fragments.InlineFileTab; - -public class FileSelect extends Activity { - public static final String RESULT_DATA = "RESULT_PATH"; - public static final String START_DATA = "START_DATA"; - public static final String WINDOW_TITLE = "WINDOW_TILE"; - public static final String NO_INLINE_SELECTION = "de.blinkt.openvpn.NO_INLINE_SELECTION"; - public static final String SHOW_CLEAR_BUTTON = "de.blinkt.openvpn.SHOW_CLEAR_BUTTON"; - public static final String DO_BASE64_ENCODE = "de.blinkt.openvpn.BASE64ENCODE"; - - private FileSelectionFragment mFSFragment; - private InlineFileTab mInlineFragment; - private String mData; - private Tab inlineFileTab; - private Tab fileExplorerTab; - private boolean mNoInline; - private boolean mShowClear; - private boolean mBase64Encode; - - - public void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - setContentView(R.layout.file_dialog); - - mData = getIntent().getStringExtra(START_DATA); - if(mData==null) - mData=Environment.getExternalStorageDirectory().getPath(); - - String title = getIntent().getStringExtra(WINDOW_TITLE); - int titleId = getIntent().getIntExtra(WINDOW_TITLE, 0); - if(titleId!=0) - title =getString(titleId); - if(title!=null) - setTitle(title); - - mNoInline = getIntent().getBooleanExtra(NO_INLINE_SELECTION, false); - mShowClear = getIntent().getBooleanExtra(SHOW_CLEAR_BUTTON, false); - mBase64Encode = getIntent().getBooleanExtra(DO_BASE64_ENCODE, false); - - ActionBar bar = getActionBar(); - bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); - fileExplorerTab = bar.newTab().setText(R.string.file_explorer_tab); - inlineFileTab = bar.newTab().setText(R.string.inline_file_tab); - - mFSFragment = new FileSelectionFragment(); - fileExplorerTab.setTabListener(new MyTabsListener(this, mFSFragment)); - bar.addTab(fileExplorerTab); - - if(!mNoInline) { - mInlineFragment = new InlineFileTab(); - inlineFileTab.setTabListener(new MyTabsListener(this, mInlineFragment)); - bar.addTab(inlineFileTab); - } else { - mFSFragment.setNoInLine(); - } - - - } - - public boolean showClear() { - if(mData == null || mData.equals("")) - return false; - else - return mShowClear; - } - - protected class MyTabsListener implements ActionBar.TabListener - { - private Fragment mFragment; - private boolean mAdded=false; - - public MyTabsListener( Activity activity, Fragment fragment){ - this.mFragment = fragment; - } - - public void onTabSelected(Tab tab, FragmentTransaction ft) { - // Check if the fragment is already initialized - if (!mAdded) { - // If not, instantiate and add it to the activity - ft.add(android.R.id.content, mFragment); - mAdded =true; - } else { - // If it exists, simply attach it in order to show it - ft.attach(mFragment); - } - } - - @Override - public void onTabUnselected(Tab tab, FragmentTransaction ft) { - ft.detach(mFragment); - } - - @Override - public void onTabReselected(Tab tab, FragmentTransaction ft) { - - } - } - - public void importFile(String path) { - File ifile = new File(path); - Exception fe = null; - try { - - String data = ""; - - byte[] fileData = readBytesFromFile(ifile) ; - if(mBase64Encode) - data += Base64.encodeToString(fileData, Base64.DEFAULT); - else - data += new String(fileData); - - mData =data; - - /* - mInlineFragment.setData(data); - getActionBar().selectTab(inlineFileTab); */ - saveInlineData(ifile.getName(), data); - } catch (FileNotFoundException e) { - fe = e; - } catch (IOException e) { - fe =e; - } - if(fe!=null) { - Builder ab = new AlertDialog.Builder(this); - ab.setTitle(R.string.error_importing_file); - ab.setMessage(getString(R.string.import_error_message) + "\n" + fe.getLocalizedMessage()); - ab.setPositiveButton(android.R.string.ok, null); - ab.show(); - } - } - - static private byte[] readBytesFromFile(File file) throws IOException { - InputStream input = new FileInputStream(file); - - long len= file.length(); - if (len > VpnProfile.MAX_EMBED_FILE_SIZE) - throw new IOException("selected file size too big to embed into profile"); - - // Create the byte array to hold the data - byte[] bytes = new byte[(int) len]; - - // Read in the bytes - int offset = 0; - int bytesRead = 0; - while (offset < bytes.length - && (bytesRead=input.read(bytes, offset, bytes.length-offset)) >= 0) { - offset += bytesRead; - } - - input.close(); - return bytes; - } - - - public void setFile(String path) { - Intent intent = new Intent(); - intent.putExtra(RESULT_DATA, path); - setResult(Activity.RESULT_OK,intent); - finish(); - } - - public String getSelectPath() { - if(VpnProfile.isEmbedded(mData)) - return mData; - else - return Environment.getExternalStorageDirectory().getPath(); - } - - public CharSequence getInlineData() { - if(VpnProfile.isEmbedded(mData)) - return VpnProfile.getEmbeddedContent(mData); - else - return ""; - } - - public void clearData() { - Intent intent = new Intent(); - intent.putExtra(RESULT_DATA, (String)null); - setResult(Activity.RESULT_OK,intent); - finish(); - - } - - public void saveInlineData(String fileName, String string) { - Intent intent = new Intent(); - - if(fileName==null) - intent.putExtra(RESULT_DATA, VpnProfile.INLINE_TAG + string); - else - intent.putExtra(RESULT_DATA,VpnProfile.DISPLAYNAME_TAG + fileName + VpnProfile.INLINE_TAG + string); - setResult(Activity.RESULT_OK, intent); - finish(); - - } -} diff --git a/app/src/main/java/de/blinkt/openvpn/activities/MainActivity.java b/app/src/main/java/de/blinkt/openvpn/activities/MainActivity.java deleted file mode 100644 index 26cd0269..00000000 --- a/app/src/main/java/de/blinkt/openvpn/activities/MainActivity.java +++ /dev/null @@ -1,103 +0,0 @@ -package de.blinkt.openvpn.activities; - -import android.app.ActionBar; -import android.app.ActionBar.Tab; -import android.app.Activity; -import android.app.Fragment; -import android.app.FragmentTransaction; -import android.content.Intent; - -import se.leap.bitmaskclient.R; -import de.blinkt.openvpn.fragments.*; - - -public class MainActivity extends Activity { - - protected void onCreate(android.os.Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - ActionBar bar = getActionBar(); - bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); - - Tab vpnListTab = bar.newTab().setText(R.string.vpn_list_title); - Tab generalTab = bar.newTab().setText(R.string.generalsettings); - Tab faqtab = bar.newTab().setText(R.string.faq); - Tab abouttab = bar.newTab().setText(R.string.about); - - vpnListTab.setTabListener(new TabListener("profiles", VPNProfileList.class)); - generalTab.setTabListener(new TabListener("settings", GeneralSettings.class)); - faqtab.setTabListener(new TabListener("faq", FaqFragment.class)); - abouttab.setTabListener(new TabListener("about", AboutFragment.class)); - - bar.addTab(vpnListTab); - bar.addTab(generalTab); - bar.addTab(faqtab); - bar.addTab(abouttab); - - if (false) { - Tab logtab = bar.newTab().setText("Log"); - logtab.setTabListener(new TabListener("log", LogFragment.class)); - bar.addTab(logtab); - } - - if(SendDumpFragment.getLastestDump(this)!=null) { - Tab sendDump = bar.newTab().setText(R.string.crashdump); - sendDump.setTabListener(new TabListener("crashdump",SendDumpFragment.class)); - bar.addTab(sendDump); - } - - } - - protected class TabListener implements ActionBar.TabListener - { - private Fragment mFragment; - private String mTag; - private Class mClass; - - public TabListener(String tag, Class clz) { - mTag = tag; - mClass = clz; - - // Check to see if we already have a fragment for this tab, probably - // from a previously saved state. If so, deactivate it, because our - // initial state is that a tab isn't shown. - mFragment = getFragmentManager().findFragmentByTag(mTag); - if (mFragment != null && !mFragment.isDetached()) { - FragmentTransaction ft = getFragmentManager().beginTransaction(); - ft.detach(mFragment); - ft.commit(); - } - } - - public void onTabSelected(Tab tab, FragmentTransaction ft) { - if (mFragment == null) { - mFragment = Fragment.instantiate(MainActivity.this, mClass.getName()); - ft.add(android.R.id.content, mFragment, mTag); - } else { - ft.attach(mFragment); - } - } - - public void onTabUnselected(Tab tab, FragmentTransaction ft) { - if (mFragment != null) { - ft.detach(mFragment); - } - } - - - @Override - public void onTabReselected(Tab tab, FragmentTransaction ft) { - - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - System.out.println(data); - - - } - - -} diff --git a/app/src/main/java/de/blinkt/openvpn/activities/VPNPreferences.java b/app/src/main/java/de/blinkt/openvpn/activities/VPNPreferences.java deleted file mode 100644 index 1ee9a5f4..00000000 --- a/app/src/main/java/de/blinkt/openvpn/activities/VPNPreferences.java +++ /dev/null @@ -1,165 +0,0 @@ -package de.blinkt.openvpn.activities; - -import java.util.List; - -import android.annotation.TargetApi; -import android.app.AlertDialog; -import android.app.Fragment; -import android.content.DialogInterface; -import android.content.Intent; -import android.os.Build; -import android.os.Bundle; -import android.preference.PreferenceActivity; -import android.preference.PreferenceFragment; -import android.view.Menu; -import android.view.MenuItem; - -import se.leap.bitmaskclient.R; -import de.blinkt.openvpn.VpnProfile; -import de.blinkt.openvpn.core.ProfileManager; -import de.blinkt.openvpn.fragments.Settings_Authentication; -import de.blinkt.openvpn.fragments.Settings_Basic; -import de.blinkt.openvpn.fragments.Settings_IP; -import de.blinkt.openvpn.fragments.Settings_Obscure; -import de.blinkt.openvpn.fragments.Settings_Routing; -import de.blinkt.openvpn.fragments.ShowConfigFragment; -import de.blinkt.openvpn.fragments.VPNProfileList; - - -public class VPNPreferences extends PreferenceActivity { - - static final Class validFragments[] = new Class[] { - Settings_Authentication.class, Settings_Basic.class, Settings_IP.class, - Settings_Obscure.class, Settings_Routing.class, ShowConfigFragment.class - }; - - private String mProfileUUID; - private VpnProfile mProfile; - - public VPNPreferences() { - super(); - } - - - @TargetApi(Build.VERSION_CODES.KITKAT) - @Override - protected boolean isValidFragment(String fragmentName) { - for (Class c: validFragments) - if (c.getName().equals(fragmentName)) - return true; - return false; - - } - - @Override - protected void onStop() { - super.onStop(); - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - outState.putString(getIntent().getStringExtra(getPackageName() + ".profileUUID"),mProfileUUID); - super.onSaveInstanceState(outState); - } - - @Override - protected void onResume() { - super.onResume(); - Intent intent = getIntent(); - - - if(intent!=null) { - String profileUUID = intent.getStringExtra(getPackageName() + ".profileUUID"); - if(profileUUID==null) { - Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS); - profileUUID = initialArguments.getString(getPackageName() + ".profileUUID"); - } - if(profileUUID!=null){ - - mProfileUUID = profileUUID; - mProfile = ProfileManager.get(this,mProfileUUID); - - } - } - // When a profile is deleted from a category fragment in hadset mod we need to finish - // this activity as well when returning - if (mProfile==null || mProfile.profileDleted) { - setResult(VPNProfileList.RESULT_VPN_DELETED); - finish(); - } - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - mProfileUUID = getIntent().getStringExtra(getPackageName() + ".profileUUID"); - if(savedInstanceState!=null){ - String savedUUID = savedInstanceState.getString(getPackageName() + ".profileUUID"); - if(savedUUID!=null) - mProfileUUID=savedUUID; - } - - mProfile = ProfileManager.get(this,mProfileUUID); - if(mProfile!=null) { - setTitle(getString(R.string.edit_profile_title, mProfile.getName())); - } - super.onCreate(savedInstanceState); - } - - - - @Override - public void onBuildHeaders(List
target) { - loadHeadersFromResource(R.xml.vpn_headers, target); - for (Header header : target) { - if(header.fragmentArguments==null) - header.fragmentArguments = new Bundle(); - header.fragmentArguments.putString(getPackageName() + ".profileUUID",mProfileUUID); - } - } - - @Override - public void onBackPressed() { - setResult(RESULT_OK, getIntent()); - super.onBackPressed(); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if(item.getItemId() == R.id.remove_vpn) - askProfileRemoval(); - return super.onOptionsItemSelected(item); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - - getMenuInflater().inflate(R.menu.vpnpreferences_menu, menu); - - return super.onCreateOptionsMenu(menu); - } - - private void askProfileRemoval() { - AlertDialog.Builder dialog = new AlertDialog.Builder(this); - dialog.setTitle("Confirm deletion"); - dialog.setMessage(getString(R.string.remove_vpn_query, mProfile.mName)); - - dialog.setPositiveButton(android.R.string.yes, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - removeProfile(mProfile); - } - - }); - dialog.setNegativeButton(android.R.string.no,null); - dialog.create().show(); - } - - protected void removeProfile(VpnProfile profile) { - ProfileManager.getInstance(this).removeProfile(this,profile); - setResult(VPNProfileList.RESULT_VPN_DELETED); - finish(); - - } -} - diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/AboutFragment.java b/app/src/main/java/de/blinkt/openvpn/fragments/AboutFragment.java deleted file mode 100644 index f878e4fb..00000000 --- a/app/src/main/java/de/blinkt/openvpn/fragments/AboutFragment.java +++ /dev/null @@ -1,297 +0,0 @@ -package de.blinkt.openvpn.fragments; - -import android.app.Fragment; -import android.app.PendingIntent; -import android.content.*; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager.NameNotFoundException; -import android.os.Bundle; -import android.os.IBinder; -import android.os.RemoteException; -import android.text.Html; -import android.text.SpannableString; -import android.text.Spanned; -import android.text.TextUtils; -import android.text.method.LinkMovementMethod; -import android.text.style.ClickableSpan; -import android.util.Log; -import android.util.Pair; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.webkit.WebView; -import android.widget.TextView; -import com.android.vending.billing.IInAppBillingService; -import se.leap.bitmaskclient.R; -import de.blinkt.openvpn.core.VpnStatus; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.*; - -public class AboutFragment extends Fragment implements View.OnClickListener { - - public static final String INAPPITEM_TYPE_INAPP = "inapp"; - public static final String RESPONSE_CODE = "RESPONSE_CODE"; - private static final int DONATION_CODE = 12; - private static final int BILLING_RESPONSE_RESULT_OK = 0; - private static final String RESPONSE_BUY_INTENT = "BUY_INTENT"; - private static final String[] donationSkus = { "donation1eur", "donation2eur", "donation5eur", "donation10eur", - "donation1337eur","donation23eur","donation25eur",}; - IInAppBillingService mService; - Hashtable viewToProduct = new Hashtable(); - ServiceConnection mServiceConn = new ServiceConnection() { - @Override - public void onServiceDisconnected(ComponentName name) { - mService = null; - } - - @Override - public void onServiceConnected(ComponentName name, - IBinder service) { - mService = IInAppBillingService.Stub.asInterface(service); - initGooglePlayDonation(); - - } - }; - - private void initGooglePlayDonation() { - new Thread("queryGMSInApp") { - @Override - public void run() { - initGMSDonateOptions(); - } - }.start(); - } - - private TextView gmsTextView; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getActivity().bindService(new - Intent("com.android.vending.billing.InAppBillingService.BIND"), - mServiceConn, Context.BIND_AUTO_CREATE); - - } - - @Override - public void onDestroy() { - super.onDestroy(); - if (mServiceConn != null) { - getActivity().unbindService(mServiceConn); - } - - } - - private void initGMSDonateOptions() { - try { - int billingSupported = mService.isBillingSupported(3, getActivity().getPackageName(), INAPPITEM_TYPE_INAPP); - if (billingSupported != BILLING_RESPONSE_RESULT_OK) { - Log.i("OpenVPN", "Play store billing not supported"); - return; - } - - ArrayList skuList = new ArrayList(); - Collections.addAll(skuList, donationSkus); - Bundle querySkus = new Bundle(); - querySkus.putStringArrayList("ITEM_ID_LIST", skuList); - - Bundle ownedItems = mService.getPurchases(3, getActivity().getPackageName(), INAPPITEM_TYPE_INAPP, null); - - - if (ownedItems.getInt(RESPONSE_CODE) != BILLING_RESPONSE_RESULT_OK) - return; - - final ArrayList ownedSkus = ownedItems.getStringArrayList("INAPP_PURCHASE_ITEM_LIST"); - - Bundle skuDetails = mService.getSkuDetails(3, getActivity().getPackageName(), INAPPITEM_TYPE_INAPP, querySkus); - - - if (skuDetails.getInt(RESPONSE_CODE) != BILLING_RESPONSE_RESULT_OK) - return; - - final ArrayList responseList = skuDetails.getStringArrayList("DETAILS_LIST"); - - if (getActivity() != null) { - getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - createPlayBuyOptions(ownedSkus, responseList); - - } - }); - } - - } catch (RemoteException e) { - VpnStatus.logException(e); - } - } - - private static class SkuResponse { - String title; - String price; - - SkuResponse(String p, String t) - { - title=t; - price=p; - } - } - - - - private void createPlayBuyOptions(ArrayList ownedSkus, ArrayList responseList) { - try { - Vector> gdonation = new Vector>(); - - gdonation.add(new Pair(getString(R.string.donatePlayStore),null)); - HashMap responseMap = new HashMap(); - for (String thisResponse : responseList) { - JSONObject object = new JSONObject(thisResponse); - responseMap.put( - object.getString("productId"), - new SkuResponse( - object.getString("price"), - object.getString("title"))); - - } - for (String sku: donationSkus) - if (responseMap.containsKey(sku)) - gdonation.add(getSkuTitle(sku, - responseMap.get(sku).title, responseMap.get(sku).price, ownedSkus)); - - String gmsTextString=""; - for(int i=0;i1) - gmsTextString+= ", "; - gmsTextString+=gdonation.elementAt(i).first; - } - SpannableString gmsText = new SpannableString(gmsTextString); - - - int lStart = 0; - int lEnd=0; - for(Pair item:gdonation){ - lEnd = lStart + item.first.length(); - if (item.second!=null) { - final String mSku = item.second; - ClickableSpan cspan = new ClickableSpan() - { - @Override - public void onClick(View widget) { - triggerBuy(mSku); - } - }; - gmsText.setSpan(cspan,lStart,lEnd,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - lStart = lEnd+2; // Account for ", " between items - } - - if(gmsTextView !=null) { - gmsTextView.setText(gmsText); - gmsTextView.setMovementMethod(LinkMovementMethod.getInstance()); - gmsTextView.setVisibility(View.VISIBLE); - } - - } catch (JSONException e) { - VpnStatus.logException("Parsing Play Store IAP",e); - } - - } - - private Pair getSkuTitle(final String sku, String title, String price, ArrayList ownedSkus) { - String text; - if (ownedSkus.contains(sku)) - return new Pair(getString(R.string.thanks_for_donation, price),null); - - if (price.contains("€")|| price.contains("\u20ac")) { - text= title; - } else { - text = String.format(Locale.getDefault(), "%s (%s)", title, price); - } - //return text; - return new Pair(price, sku); - - } - - private void triggerBuy(String sku) { - try { - Bundle buyBundle - = mService.getBuyIntent(3, getActivity().getPackageName(), - sku, INAPPITEM_TYPE_INAPP, "Thanks for the donation! :)"); - - - if (buyBundle.getInt(RESPONSE_CODE) == BILLING_RESPONSE_RESULT_OK) { - PendingIntent buyIntent = buyBundle.getParcelable(RESPONSE_BUY_INTENT); - getActivity().startIntentSenderForResult(buyIntent.getIntentSender(), DONATION_CODE, new Intent(), - 0, 0, 0); - } - - } catch (RemoteException e) { - VpnStatus.logException(e); - } catch (IntentSender.SendIntentException e) { - VpnStatus.logException(e); - } - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View v = inflater.inflate(R.layout.about, container, false); - TextView ver = (TextView) v.findViewById(R.id.version); - - String version; - String name = "Openvpn"; - try { - PackageInfo packageinfo = getActivity().getPackageManager().getPackageInfo(getActivity().getPackageName(), 0); - version = packageinfo.versionName; - name = getString(R.string.app); - } catch (NameNotFoundException e) { - version = "error fetching version"; - } - - - ver.setText(getString(R.string.version_info, name, version)); - - TextView paypal = (TextView) v.findViewById(R.id.donatestring); - - String donatetext = getActivity().getString(R.string.donatewithpaypal); - Spanned htmltext = Html.fromHtml(donatetext); - paypal.setText(htmltext); - paypal.setMovementMethod(LinkMovementMethod.getInstance()); - gmsTextView = (TextView) v.findViewById(R.id.donategms); - /* recreating view without onCreate/onDestroy cycle */ - if (mService!=null) - initGooglePlayDonation(); - - TextView translation = (TextView) v.findViewById(R.id.translation); - - // Don't print a text for myself - if (getString(R.string.translationby).contains("Arne Schwabe")) - translation.setText(""); - else - translation.setText(R.string.translationby); - - WebView wv = (WebView)v.findViewById(R.id.webView); - wv.loadUrl("file:///android_asset/full_licenses.html"); - - return v; - } - - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - if (mService!=null) - initGooglePlayDonation(); - } - - - @Override - public void onClick(View v) { - - } -} diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/FaqFragment.java b/app/src/main/java/de/blinkt/openvpn/fragments/FaqFragment.java deleted file mode 100644 index ef57a395..00000000 --- a/app/src/main/java/de/blinkt/openvpn/fragments/FaqFragment.java +++ /dev/null @@ -1,36 +0,0 @@ -package de.blinkt.openvpn.fragments; - -import android.app.Fragment; -import android.os.Bundle; -import android.text.Html; -import android.text.method.LinkMovementMethod; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; -import se.leap.bitmaskclient.R; - -public class FaqFragment extends Fragment { - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View v= inflater.inflate(R.layout.faq, container, false); - - insertHtmlEntry(v,R.id.broken_images_faq,R.string.broken_images_faq); - insertHtmlEntry(v,R.id.faq_howto,R.string.faq_howto); - insertHtmlEntry(v, R.id.baterry_consumption, R.string.baterry_consumption); - insertHtmlEntry(v, R.id.faq_tethering, R.string.faq_tethering); - insertHtmlEntry(v, R.id.faq_vpndialog43, R.string.faq_vpndialog43); - insertHtmlEntry(v, R.id.faq_system_dialog_xposed, R.string.faq_system_dialog_xposed); - return v; - } - - private void insertHtmlEntry (View v, int viewId, int stringId) { - TextView faqitem = (TextView) v.findViewById(viewId); - faqitem.setText(Html.fromHtml(getActivity().getString(stringId))); - faqitem.setMovementMethod(LinkMovementMethod.getInstance()); - - } - -} diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/FileSelectionFragment.java b/app/src/main/java/de/blinkt/openvpn/fragments/FileSelectionFragment.java deleted file mode 100644 index 271af173..00000000 --- a/app/src/main/java/de/blinkt/openvpn/fragments/FileSelectionFragment.java +++ /dev/null @@ -1,256 +0,0 @@ -package de.blinkt.openvpn.fragments; - -import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.TreeMap; - -import android.app.AlertDialog; -import android.app.ListFragment; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.CheckBox; -import android.widget.ListView; -import android.widget.SimpleAdapter; -import android.widget.TextView; -import de.blinkt.openvpn.activities.FileSelect; -import se.leap.bitmaskclient.R; - -public class FileSelectionFragment extends ListFragment { - - private static final String ITEM_KEY = "key"; - private static final String ITEM_IMAGE = "image"; - private static final String ROOT = "/"; - - - private List path = null; - private TextView myPath; - private ArrayList> mList; - - private Button selectButton; - - - private String parentPath; - private String currentPath = ROOT; - - - private String[] formatFilter = null; - - private File selectedFile; - private HashMap lastPositions = new HashMap(); - private String mStartPath; - private CheckBox mInlineImport; - private Button mClearButton; - private boolean mHideImport=false; - - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View v = inflater.inflate(R.layout.file_dialog_main, container,false); - - myPath = (TextView) v.findViewById(R.id.path); - - mInlineImport = (CheckBox) v.findViewById(R.id.doinline); - - if(mHideImport) { - mInlineImport.setVisibility(View.GONE); - mInlineImport.setChecked(false); - } - - - - selectButton = (Button) v.findViewById(R.id.fdButtonSelect); - selectButton.setEnabled(false); - selectButton.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - if (selectedFile != null) { - if(mInlineImport.isChecked()) - - ((FileSelect) getActivity()).importFile(selectedFile.getPath()); - else - ((FileSelect) getActivity()).setFile(selectedFile.getPath()); - } - } - }); - - mClearButton = (Button) v.findViewById(R.id.fdClear); - mClearButton.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - ((FileSelect) getActivity()).clearData(); - } - }); - if(!((FileSelect) getActivity()).showClear()) { - mClearButton.setVisibility(View.GONE); - mClearButton.setEnabled(false); - } - - return v; - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - mStartPath = ((FileSelect) getActivity()).getSelectPath(); - getDir(mStartPath); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - } - - private void getDir(String dirPath) { - - boolean useAutoSelection = dirPath.length() < currentPath.length(); - - Integer position = lastPositions.get(parentPath); - - getDirImpl(dirPath); - - if (position != null && useAutoSelection) { - getListView().setSelection(position); - } - - } - - /** - * Monta a estrutura de arquivos e diretorios filhos do diretorio fornecido. - * - * @param dirPath - * Diretorio pai. - */ - private void getDirImpl(final String dirPath) { - - currentPath = dirPath; - - final List item = new ArrayList(); - path = new ArrayList(); - mList = new ArrayList>(); - - File f = new File(currentPath); - File[] files = f.listFiles(); - if (files == null) { - currentPath = ROOT; - f = new File(currentPath); - files = f.listFiles(); - } - - myPath.setText(getText(R.string.location) + ": " + currentPath); - - if (!currentPath.equals(ROOT)) { - - item.add(ROOT); - addItem(ROOT, R.drawable.ic_root_folder_am); - path.add(ROOT); - - item.add("../"); - addItem("../", R.drawable.ic_root_folder_am); - path.add(f.getParent()); - parentPath = f.getParent(); - - } - - TreeMap dirsMap = new TreeMap(); - TreeMap dirsPathMap = new TreeMap(); - TreeMap filesMap = new TreeMap(); - TreeMap filesPathMap = new TreeMap(); - for (File file : files) { - if (file.isDirectory()) { - String dirName = file.getName(); - dirsMap.put(dirName, dirName); - dirsPathMap.put(dirName, file.getPath()); - } else { - final String fileName = file.getName(); - final String fileNameLwr = fileName.toLowerCase(Locale.getDefault()); - // se ha um filtro de formatos, utiliza-o - if (formatFilter != null) { - boolean contains = false; - for (String aFormatFilter : formatFilter) { - final String formatLwr = aFormatFilter.toLowerCase(Locale.getDefault()); - if (fileNameLwr.endsWith(formatLwr)) { - contains = true; - break; - } - } - if (contains) { - filesMap.put(fileName, fileName); - filesPathMap.put(fileName, file.getPath()); - } - // senao, adiciona todos os arquivos - } else { - filesMap.put(fileName, fileName); - filesPathMap.put(fileName, file.getPath()); - } - } - } - item.addAll(dirsMap.tailMap("").values()); - item.addAll(filesMap.tailMap("").values()); - path.addAll(dirsPathMap.tailMap("").values()); - path.addAll(filesPathMap.tailMap("").values()); - - SimpleAdapter fileList = new SimpleAdapter(getActivity(), mList, R.layout.file_dialog_row, new String[] { - ITEM_KEY, ITEM_IMAGE }, new int[] { R.id.fdrowtext, R.id.fdrowimage }); - - for (String dir : dirsMap.tailMap("").values()) { - addItem(dir, R.drawable.ic_root_folder_am); - } - - for (String file : filesMap.tailMap("").values()) { - addItem(file, R.drawable.ic_doc_generic_am); - } - - fileList.notifyDataSetChanged(); - - setListAdapter(fileList); - - } - - private void addItem(String fileName, int imageId) { - HashMap item = new HashMap(); - item.put(ITEM_KEY, fileName); - item.put(ITEM_IMAGE, imageId); - mList.add(item); - } - - - @Override - public void onListItemClick(ListView l, View v, int position, long id) { - - File file = new File(path.get(position)); - - if (file.isDirectory()) { - selectButton.setEnabled(false); - - if (file.canRead()) { - lastPositions.put(currentPath, position); - getDir(path.get(position)); - } else { - new AlertDialog.Builder(getActivity()).setIcon(R.drawable.icon) - .setTitle("[" + file.getName() + "] " + getText(R.string.cant_read_folder)) - .setPositiveButton("OK", null).show(); - } - } else { - selectedFile = file; - v.setSelected(true); - selectButton.setEnabled(true); - } - } - - public void setNoInLine() { - mHideImport=true; - - } - -} diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/InlineFileTab.java b/app/src/main/java/de/blinkt/openvpn/fragments/InlineFileTab.java deleted file mode 100644 index f0aa4fe2..00000000 --- a/app/src/main/java/de/blinkt/openvpn/fragments/InlineFileTab.java +++ /dev/null @@ -1,66 +0,0 @@ -package de.blinkt.openvpn.fragments; - -import android.app.Fragment; -import android.os.Bundle; -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.EditText; -import de.blinkt.openvpn.activities.FileSelect; -import se.leap.bitmaskclient.R; - -public class InlineFileTab extends Fragment -{ - - private static final int MENU_SAVE = 0; - private EditText mInlineData; - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - mInlineData.setText(((FileSelect)getActivity()).getInlineData()); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) - { - - View v = inflater.inflate(R.layout.file_dialog_inline, container, false); - mInlineData =(EditText) v.findViewById(R.id.inlineFileData); - return v; - } - - public void setData(String data) { - if(mInlineData!=null) - mInlineData.setText(data); - - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - menu.add(0, MENU_SAVE, 0, "Use inline data") - .setIcon(android.R.drawable.ic_menu_save) - .setAlphabeticShortcut('u') - .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM - | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - } - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if(item.getItemId()==MENU_SAVE){ - ((FileSelect)getActivity()).saveInlineData(null, mInlineData.getText().toString()); - return true; - } - return super.onOptionsItemSelected(item); - } - -} \ No newline at end of file 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 386e3133..fe7a8bd3 100644 --- a/app/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java +++ b/app/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java @@ -1,5 +1,7 @@ package de.blinkt.openvpn.fragments; +import se.leap.bitmaskclient.R; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/OpenVpnPreferencesFragment.java b/app/src/main/java/de/blinkt/openvpn/fragments/OpenVpnPreferencesFragment.java deleted file mode 100644 index 4a6f19b5..00000000 --- a/app/src/main/java/de/blinkt/openvpn/fragments/OpenVpnPreferencesFragment.java +++ /dev/null @@ -1,48 +0,0 @@ -package de.blinkt.openvpn.fragments; - -import android.os.Bundle; -import android.preference.PreferenceFragment; -import se.leap.bitmaskclient.R; -import de.blinkt.openvpn.VpnProfile; -import de.blinkt.openvpn.core.ProfileManager; - -public abstract class OpenVpnPreferencesFragment extends PreferenceFragment { - - protected VpnProfile mProfile; - - protected abstract void loadSettings(); - protected abstract void saveSettings(); - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - String profileUUID = getArguments().getString(getActivity().getPackageName() + ".profileUUID"); - mProfile = ProfileManager.get(getActivity(),profileUUID); - getActivity().setTitle(getString(R.string.edit_profile_title, mProfile.getName())); - - } - - @Override - public void onPause() { - super.onPause(); - saveSettings(); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - if(savedInstanceState!=null) { - String profileUUID=savedInstanceState.getString(VpnProfile.EXTRA_PROFILEUUID); - mProfile = ProfileManager.get(getActivity(),profileUUID); - loadSettings(); - } - } - - @Override - public void onSaveInstanceState (Bundle outState) { - super.onSaveInstanceState(outState); - saveSettings(); - outState.putString(VpnProfile.EXTRA_PROFILEUUID, mProfile.getUUIDString()); - } -} diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/SendDumpFragment.java b/app/src/main/java/de/blinkt/openvpn/fragments/SendDumpFragment.java deleted file mode 100644 index fd01b89f..00000000 --- a/app/src/main/java/de/blinkt/openvpn/fragments/SendDumpFragment.java +++ /dev/null @@ -1,94 +0,0 @@ -package de.blinkt.openvpn.fragments; - -import java.io.File; -import java.util.ArrayList; - -import android.app.Fragment; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager.NameNotFoundException; -import android.net.Uri; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import se.leap.bitmaskclient.R; -import de.blinkt.openvpn.core.VpnStatus; - -public class SendDumpFragment extends Fragment { - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - - View v = inflater.inflate(R.layout.fragment_senddump, container, false); - v.findViewById(R.id.senddump).setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - emailMiniDumps(); - } - }); - return v; - } - - public void emailMiniDumps() - { - //need to "send multiple" to get more than one attachment - final Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND_MULTIPLE); - emailIntent.setType("*/*"); - emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL, - new String[]{"Arne Schwabe "}); - - String version; - String name="ics-openvpn"; - try { - PackageInfo packageinfo = getActivity().getPackageManager().getPackageInfo(getActivity().getPackageName(), 0); - version = packageinfo.versionName; - name = packageinfo.applicationInfo.name; - } catch (NameNotFoundException e) { - version = "error fetching version"; - } - - - emailIntent.putExtra(Intent.EXTRA_SUBJECT, String.format("%s %s Minidump",name,version)); - - emailIntent.putExtra(Intent.EXTRA_TEXT, "Please describe the issue you have experienced"); - - ArrayList uris = new ArrayList(); - - File ldump = getLastestDump(getActivity()); - if(ldump==null) { - VpnStatus.logError("No Minidump found!"); - } - - uris.add(Uri.parse("content://de.blinkt.openvpn.FileProvider/" + ldump.getName())); - uris.add(Uri.parse("content://de.blinkt.openvpn.FileProvider/" + ldump.getName() + ".log")); - - emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - emailIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); - startActivity(emailIntent); - } - - static public File getLastestDump(Context c) { - long newestDumpTime=0; - File newestDumpFile=null; - - for(File f:c.getCacheDir().listFiles()) { - if(!f.getName().endsWith(".dmp")) - continue; - - if (newestDumpTime < f.lastModified()) { - newestDumpTime = f.lastModified(); - newestDumpFile=f; - } - } - // Ignore old dumps - //if(System.currentTimeMillis() - 48 * 60 * 1000 > newestDumpTime ) - //return null; - - return newestDumpFile; - } -} diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Authentication.java b/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Authentication.java deleted file mode 100644 index 9bd3bf54..00000000 --- a/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Authentication.java +++ /dev/null @@ -1,214 +0,0 @@ -package de.blinkt.openvpn.fragments; - -import android.app.Activity; -import android.content.Intent; -import android.os.Build; -import android.os.Bundle; -import android.preference.CheckBoxPreference; -import android.preference.EditTextPreference; -import android.preference.ListPreference; -import android.preference.Preference; -import android.preference.Preference.OnPreferenceChangeListener; -import android.preference.Preference.OnPreferenceClickListener; -import android.preference.SwitchPreference; -import android.util.Pair; -import de.blinkt.openvpn.activities.FileSelect; -import se.leap.bitmaskclient.R; -import de.blinkt.openvpn.core.VpnStatus; -import de.blinkt.openvpn.views.RemoteCNPreference; -import de.blinkt.openvpn.VpnProfile; - -import java.io.IOException; - - -public class Settings_Authentication extends OpenVpnPreferencesFragment implements OnPreferenceChangeListener, OnPreferenceClickListener { - private static final int SELECT_TLS_FILE = 23223232; - private static final int SELECT_TLS_FILE_KITKAT = SELECT_TLS_FILE +1; - private CheckBoxPreference mExpectTLSCert; - private CheckBoxPreference mCheckRemoteCN; - private RemoteCNPreference mRemoteCN; - private ListPreference mTLSAuthDirection; - private Preference mTLSAuthFile; - private SwitchPreference mUseTLSAuth; - private EditTextPreference mCipher; - private String mTlsAuthFileData; - private EditTextPreference mAuth; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // Load the preferences from an XML resource - addPreferencesFromResource(R.xml.vpn_authentification); - - mExpectTLSCert = (CheckBoxPreference) findPreference("remoteServerTLS"); - mCheckRemoteCN = (CheckBoxPreference) findPreference("checkRemoteCN"); - mRemoteCN = (RemoteCNPreference) findPreference("remotecn"); - mRemoteCN.setOnPreferenceChangeListener(this); - - mUseTLSAuth = (SwitchPreference) findPreference("useTLSAuth" ); - mTLSAuthFile = findPreference("tlsAuthFile"); - mTLSAuthDirection = (ListPreference) findPreference("tls_direction"); - - - mTLSAuthFile.setOnPreferenceClickListener(this); - - mCipher =(EditTextPreference) findPreference("cipher"); - mCipher.setOnPreferenceChangeListener(this); - - mAuth =(EditTextPreference) findPreference("auth"); - mAuth.setOnPreferenceChangeListener(this); - - loadSettings(); - - } - - @Override - protected void loadSettings() { - - mExpectTLSCert.setChecked(mProfile.mExpectTLSCert); - mCheckRemoteCN.setChecked(mProfile.mCheckRemoteCN); - mRemoteCN.setDN(mProfile.mRemoteCN); - mRemoteCN.setAuthType(mProfile.mX509AuthType); - onPreferenceChange(mRemoteCN, - new Pair(mProfile.mX509AuthType, mProfile.mRemoteCN)); - - mUseTLSAuth.setChecked(mProfile.mUseTLSAuth); - mTlsAuthFileData= mProfile.mTLSAuthFilename; - setTlsAuthSummary(mTlsAuthFileData); - mTLSAuthDirection.setValue(mProfile.mTLSAuthDirection); - mCipher.setText(mProfile.mCipher); - onPreferenceChange(mCipher, mProfile.mCipher); - mAuth.setText(mProfile.mAuth); - onPreferenceChange(mAuth, mProfile.mAuth); - - if (mProfile.mAuthenticationType == VpnProfile.TYPE_STATICKEYS) { - mExpectTLSCert.setEnabled(false); - mCheckRemoteCN.setEnabled(false); - mUseTLSAuth.setChecked(true); - } else { - mExpectTLSCert.setEnabled(true); - mCheckRemoteCN.setEnabled(true); - - } - } - - @Override - protected void saveSettings() { - mProfile.mExpectTLSCert=mExpectTLSCert.isChecked(); - mProfile.mCheckRemoteCN=mCheckRemoteCN.isChecked(); - mProfile.mRemoteCN=mRemoteCN.getCNText(); - mProfile.mX509AuthType=mRemoteCN.getAuthtype(); - - mProfile.mUseTLSAuth = mUseTLSAuth.isChecked(); - mProfile.mTLSAuthFilename = mTlsAuthFileData; - - if(mTLSAuthDirection.getValue()==null) - mProfile.mTLSAuthDirection=null; - else - mProfile.mTLSAuthDirection = mTLSAuthDirection.getValue(); - - if(mCipher.getText()==null) - mProfile.mCipher=null; - else - mProfile.mCipher = mCipher.getText(); - - if(mAuth.getText()==null) - mProfile.mAuth = null; - else - mProfile.mAuth = mAuth.getText(); - - } - - - - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - if(preference==mRemoteCN) { - @SuppressWarnings("unchecked") - int authtype = ((Pair) newValue).first; - @SuppressWarnings("unchecked") - String dn = ((Pair) newValue).second; - - if ("".equals(dn)) - preference.setSummary(getX509String(VpnProfile.X509_VERIFY_TLSREMOTE_RDN, mProfile.mServerName)); - else - preference.setSummary(getX509String(authtype,dn)); - - } else if (preference == mCipher || preference == mAuth) { - preference.setSummary((CharSequence) newValue); - } - return true; - } - private CharSequence getX509String(int authtype, String dn) { - String ret =""; - switch (authtype) { - case VpnProfile.X509_VERIFY_TLSREMOTE: - case VpnProfile.X509_VERIFY_TLSREMOTE_COMPAT_NOREMAPPING: - ret+="tls-remote "; - break; - - case VpnProfile.X509_VERIFY_TLSREMOTE_DN: - ret="dn: "; - break; - - case VpnProfile.X509_VERIFY_TLSREMOTE_RDN: - ret="rdn: "; - break; - - case VpnProfile.X509_VERIFY_TLSREMOTE_RDN_PREFIX: - ret="rdn prefix: "; - break; - } - return ret + dn; - } - - void startFileDialog() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - Intent startFC = Utils.getFilePickerIntent (getActivity(), Utils.FileType.TLS_AUTH_FILE); - startActivityForResult(startFC, SELECT_TLS_FILE_KITKAT); - } else { - Intent startFC = new Intent(getActivity(), FileSelect.class); - startFC.putExtra(FileSelect.START_DATA, mTlsAuthFileData); - startFC.putExtra(FileSelect.WINDOW_TITLE, R.string.tls_auth_file); - startActivityForResult(startFC, SELECT_TLS_FILE); - } - } - - @Override - public boolean onPreferenceClick(Preference preference) { - startFileDialog(); - return true; - - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if(requestCode==SELECT_TLS_FILE && resultCode == Activity.RESULT_OK){ - String result = data.getStringExtra(FileSelect.RESULT_DATA); - mTlsAuthFileData=result; - setTlsAuthSummary(result); - } else if (requestCode == SELECT_TLS_FILE_KITKAT && resultCode == Activity.RESULT_OK) { - try { - mTlsAuthFileData= Utils.getFilePickerResult(Utils.FileType.TLS_AUTH_FILE,data,getActivity()); - setTlsAuthSummary(mTlsAuthFileData); - } catch (IOException e) { - VpnStatus.logException(e); - } catch (SecurityException se) { - VpnStatus.logException(se); - } - } - } - - private void setTlsAuthSummary(String result) { - if(result==null) - result = getString(R.string.no_certificate); - if(result.startsWith(VpnProfile.INLINE_TAG)) - mTLSAuthFile.setSummary(R.string.inline_file_data); - else if (result.startsWith(VpnProfile.DISPLAYNAME_TAG)) - mExpectTLSCert.setSummary(getString(R.string.imported_from_file, VpnProfile.getDisplayName(result))); - else - mTLSAuthFile.setSummary(result); - } -} \ No newline at end of file diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Basic.java b/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Basic.java deleted file mode 100644 index 203d6c5c..00000000 --- a/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Basic.java +++ /dev/null @@ -1,360 +0,0 @@ -package de.blinkt.openvpn.fragments; - -import android.annotation.TargetApi; -import android.app.Activity; -import android.app.AlertDialog; -import android.app.AlertDialog.Builder; -import android.app.Fragment; -import android.content.ActivityNotFoundException; -import android.content.Intent; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.Handler.Callback; -import android.os.Message; -import android.security.KeyChain; -import android.security.KeyChainAliasCallback; -import android.security.KeyChainException; -import android.util.SparseArray; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemSelectedListener; -import android.widget.CheckBox; -import android.widget.EditText; -import android.widget.Spinner; -import android.widget.TextView; -import android.widget.ToggleButton; -import de.blinkt.openvpn.views.FileSelectLayout; -import se.leap.bitmaskclient.R; -import de.blinkt.openvpn.VpnProfile; -import se.leap.bitmaskclient.R.id; -import de.blinkt.openvpn.core.ProfileManager; -import de.blinkt.openvpn.core.X509Utils; - -import java.security.cert.X509Certificate; - -public class Settings_Basic extends Fragment implements View.OnClickListener, OnItemSelectedListener, Callback, FileSelectLayout.FileSelectCallback { - private static final int CHOOSE_FILE_OFFSET = 1000; - private static final int UPDATE_ALIAS = 20; - - private TextView mServerAddress; - private TextView mServerPort; - private FileSelectLayout mClientCert; - private FileSelectLayout mCaCert; - private FileSelectLayout mClientKey; - private TextView mAliasName; - private TextView mAliasCertificate; - private CheckBox mUseLzo; - private ToggleButton mTcpUdp; - private Spinner mType; - private FileSelectLayout mpkcs12; - private TextView mPKCS12Password; - private Handler mHandler; - private EditText mUserName; - private EditText mPassword; - private View mView; - private VpnProfile mProfile; - private EditText mProfileName; - private EditText mKeyPassword; - - private SparseArray fileselects = new SparseArray(); - - - - private void addFileSelectLayout (FileSelectLayout fsl, Utils.FileType type) { - int i = fileselects.size() + CHOOSE_FILE_OFFSET; - fileselects.put(i, fsl); - fsl.setCaller(this, i, type); - } - - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - String profileUuid = getArguments().getString(getActivity().getPackageName() + ".profileUUID"); - mProfile=ProfileManager.get(getActivity(),profileUuid); - getActivity().setTitle(getString(R.string.edit_profile_title, mProfile.getName())); - } - - - private void setKeystoreCertficate() - { - new Thread() { - public void run() { - String certstr=""; - try { - X509Certificate cert = KeyChain.getCertificateChain(getActivity(), mProfile.mAlias)[0]; - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - { - if (isInHardwareKeystore()) - certstr+=getString(R.string.hwkeychain); - } - } - - certstr+=X509Utils.getCertificateFriendlyName(cert); - } catch (Exception e) { - certstr="Could not get certificate from Keystore: " +e.getLocalizedMessage(); - } - - final String certStringCopy=certstr; - getActivity().runOnUiThread(new Runnable() { - - @Override - public void run() { - mAliasCertificate.setText(certStringCopy); - } - }); - - } - }.start(); - } - - @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) - private boolean isInHardwareKeystore() throws KeyChainException, InterruptedException { - String algorithm = KeyChain.getPrivateKey(getActivity(), mProfile.mAlias).getAlgorithm(); - return KeyChain.isBoundKeyAlgorithm(algorithm); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - - - mView = inflater.inflate(R.layout.basic_settings,container,false); - - mProfileName = (EditText) mView.findViewById(R.id.profilename); - mServerAddress = (TextView) mView.findViewById(R.id.address); - mServerPort = (TextView) mView.findViewById(R.id.port); - mClientCert = (FileSelectLayout) mView.findViewById(R.id.certselect); - mClientKey = (FileSelectLayout) mView.findViewById(R.id.keyselect); - mCaCert = (FileSelectLayout) mView.findViewById(R.id.caselect); - mpkcs12 = (FileSelectLayout) mView.findViewById(R.id.pkcs12select); - mUseLzo = (CheckBox) mView.findViewById(R.id.lzo); - mTcpUdp = (ToggleButton) mView.findViewById(id.tcpudp); - mType = (Spinner) mView.findViewById(R.id.type); - mPKCS12Password = (TextView) mView.findViewById(R.id.pkcs12password); - mAliasName = (TextView) mView.findViewById(R.id.aliasname); - mAliasCertificate = (TextView) mView.findViewById(id.alias_certificate); - - mUserName = (EditText) mView.findViewById(R.id.auth_username); - mPassword = (EditText) mView.findViewById(R.id.auth_password); - mKeyPassword = (EditText) mView.findViewById(R.id.key_password); - - addFileSelectLayout(mCaCert, Utils.FileType.CA_CERTIFICATE); - addFileSelectLayout(mClientCert, Utils.FileType.CLIENT_CERTIFICATE); - addFileSelectLayout(mClientKey, Utils.FileType.KEYFILE); - addFileSelectLayout(mpkcs12, Utils.FileType.PKCS12); - mCaCert.setShowClear(); - - mType.setOnItemSelectedListener(this); - - mView.findViewById(R.id.select_keystore_button).setOnClickListener(this); - - - if (mHandler == null) { - mHandler = new Handler(this); - } - - return mView; - } - - - @Override - public void onStart() { - super.onStart(); - String profileUuid =getArguments().getString(getActivity().getPackageName() + ".profileUUID"); - mProfile=ProfileManager.get(getActivity(),profileUuid); - loadPreferences(); - - } - - @Override - public void onActivityResult(int request, int result, Intent data) { - if (result == Activity.RESULT_OK && request >= CHOOSE_FILE_OFFSET) { - - FileSelectLayout fsl = fileselects.get(request); - fsl.parseResponse(data, getActivity()); - - savePreferences(); - - // Private key files may result in showing/hiding the private key password dialog - if(fsl==mClientKey) { - changeType(mType.getSelectedItemPosition()); - } - } - - } - - - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - if (parent == mType) { - changeType(position); - } - } - @Override - public void onPause() { - super.onPause(); - savePreferences(); - } - - - - private void changeType(int type){ - // hide everything - mView.findViewById(R.id.pkcs12).setVisibility(View.GONE); - mView.findViewById(R.id.certs).setVisibility(View.GONE); - mView.findViewById(R.id.statickeys).setVisibility(View.GONE); - mView.findViewById(R.id.keystore).setVisibility(View.GONE); - mView.findViewById(R.id.cacert).setVisibility(View.GONE); - mView.findViewById(R.id.userpassword).setVisibility(View.GONE); - mView.findViewById(R.id.key_password_layout).setVisibility(View.GONE); - - // Fall through are by design - switch(type) { - case VpnProfile.TYPE_USERPASS_CERTIFICATES: - mView.findViewById(R.id.userpassword).setVisibility(View.VISIBLE); - case VpnProfile.TYPE_CERTIFICATES: - mView.findViewById(R.id.certs).setVisibility(View.VISIBLE); - mView.findViewById(R.id.cacert).setVisibility(View.VISIBLE); - if(mProfile.requireTLSKeyPassword()) - mView.findViewById(R.id.key_password_layout).setVisibility(View.VISIBLE); - break; - - case VpnProfile.TYPE_USERPASS_PKCS12: - mView.findViewById(R.id.userpassword).setVisibility(View.VISIBLE); - case VpnProfile.TYPE_PKCS12: - mView.findViewById(R.id.pkcs12).setVisibility(View.VISIBLE); - break; - - case VpnProfile.TYPE_STATICKEYS: - mView.findViewById(R.id.statickeys).setVisibility(View.VISIBLE); - break; - - case VpnProfile.TYPE_USERPASS_KEYSTORE: - mView.findViewById(R.id.userpassword).setVisibility(View.VISIBLE); - case VpnProfile.TYPE_KEYSTORE: - mView.findViewById(R.id.keystore).setVisibility(View.VISIBLE); - mView.findViewById(R.id.cacert).setVisibility(View.VISIBLE); - break; - - case VpnProfile.TYPE_USERPASS: - mView.findViewById(R.id.userpassword).setVisibility(View.VISIBLE); - mView.findViewById(R.id.cacert).setVisibility(View.VISIBLE); - break; - } - - - } - - private void loadPreferences() { - mProfileName.setText(mProfile.mName); - mClientCert.setData(mProfile.mClientCertFilename, getActivity()); - mClientKey.setData(mProfile.mClientKeyFilename, getActivity()); - mCaCert.setData(mProfile.mCaFilename, getActivity()); - - mUseLzo.setChecked(mProfile.mUseLzo); - mServerPort.setText(mProfile.mServerPort); - mServerAddress.setText(mProfile.mServerName); - mTcpUdp.setChecked(mProfile.mUseUdp); - mType.setSelection(mProfile.mAuthenticationType); - mpkcs12.setData(mProfile.mPKCS12Filename, getActivity()); - mPKCS12Password.setText(mProfile.mPKCS12Password); - mUserName.setText(mProfile.mUsername); - mPassword.setText(mProfile.mPassword); - mKeyPassword.setText(mProfile.mKeyPassword); - - setAlias(); - - } - - void savePreferences() { - - mProfile.mName = mProfileName.getText().toString(); - mProfile.mCaFilename = mCaCert.getData(); - mProfile.mClientCertFilename = mClientCert.getData(); - mProfile.mClientKeyFilename = mClientKey.getData(); - - mProfile.mUseLzo = mUseLzo.isChecked(); - mProfile.mServerPort =mServerPort.getText().toString(); - mProfile.mServerName = mServerAddress.getText().toString(); - mProfile.mUseUdp = mTcpUdp.isChecked(); - - mProfile.mAuthenticationType = mType.getSelectedItemPosition(); - mProfile.mPKCS12Filename = mpkcs12.getData(); - mProfile.mPKCS12Password = mPKCS12Password.getText().toString(); - - mProfile.mPassword = mPassword.getText().toString(); - mProfile.mUsername = mUserName.getText().toString(); - mProfile.mKeyPassword = mKeyPassword.getText().toString(); - - } - - - private void setAlias() { - if(mProfile.mAlias == null) { - mAliasName.setText(R.string.client_no_certificate); - mAliasCertificate.setText(""); - } else { - mAliasCertificate.setText("Loading certificate from Keystore..."); - mAliasName.setText(mProfile.mAlias); - setKeystoreCertficate(); - } - } - - public void showCertDialog () { - try { - KeyChain.choosePrivateKeyAlias(getActivity(), - new KeyChainAliasCallback() { - - public void alias(String alias) { - // Credential alias selected. Remember the alias selection for future use. - mProfile.mAlias=alias; - mHandler.sendEmptyMessage(UPDATE_ALIAS); - } - - - }, - new String[] {"RSA"}, // List of acceptable key types. null for any - null, // issuer, null for any - mProfile.mServerName, // host name of server requesting the cert, null if unavailable - -1, // port of server requesting the cert, -1 if unavailable - mProfile.mAlias); // alias to preselect, null if unavailable - } catch (ActivityNotFoundException anf) { - Builder ab = new AlertDialog.Builder(getActivity()); - ab.setTitle(R.string.broken_image_cert_title); - ab.setMessage(R.string.broken_image_cert); - ab.setPositiveButton(android.R.string.ok, null); - ab.show(); - } - } - - @Override - public void onClick(View v) { - if (v == mView.findViewById(R.id.select_keystore_button)) { - showCertDialog(); - } - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - savePreferences(); - if(mProfile!=null) { - outState.putString(getActivity().getPackageName() + "profileUUID", mProfile.getUUID().toString()); - } - } - - @Override - public void onNothingSelected(AdapterView parent) { - } - - - @Override - public boolean handleMessage(Message msg) { - setAlias(); - return true; - } - - -} diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/Settings_IP.java b/app/src/main/java/de/blinkt/openvpn/fragments/Settings_IP.java deleted file mode 100644 index 81988012..00000000 --- a/app/src/main/java/de/blinkt/openvpn/fragments/Settings_IP.java +++ /dev/null @@ -1,130 +0,0 @@ -package de.blinkt.openvpn.fragments; -import android.os.Bundle; -import android.preference.CheckBoxPreference; -import android.preference.EditTextPreference; -import android.preference.Preference; -import android.preference.Preference.OnPreferenceChangeListener; -import android.preference.PreferenceManager; -import android.preference.SwitchPreference; -import se.leap.bitmaskclient.R; -import de.blinkt.openvpn.VpnProfile; - -public class Settings_IP extends OpenVpnPreferencesFragment implements OnPreferenceChangeListener { - private EditTextPreference mIPv4; - private EditTextPreference mIPv6; - private SwitchPreference mUsePull; - private CheckBoxPreference mOverrideDNS; - private EditTextPreference mSearchdomain; - private EditTextPreference mDNS1; - private EditTextPreference mDNS2; - private CheckBoxPreference mNobind; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - - // Make sure default values are applied. In a real app, you would - // want this in a shared function that is used to retrieve the - // SharedPreferences wherever they are needed. - PreferenceManager.setDefaultValues(getActivity(), - R.xml.vpn_ipsettings, false); - - // Load the preferences from an XML resource - addPreferencesFromResource(R.xml.vpn_ipsettings); - mIPv4 = (EditTextPreference) findPreference("ipv4_address"); - mIPv6 = (EditTextPreference) findPreference("ipv6_address"); - mUsePull = (SwitchPreference) findPreference("usePull"); - mOverrideDNS = (CheckBoxPreference) findPreference("overrideDNS"); - mSearchdomain =(EditTextPreference) findPreference("searchdomain"); - mDNS1 = (EditTextPreference) findPreference("dns1"); - mDNS2 = (EditTextPreference) findPreference("dns2"); - mNobind = (CheckBoxPreference) findPreference("nobind"); - - mIPv4.setOnPreferenceChangeListener(this); - mIPv6.setOnPreferenceChangeListener(this); - mDNS1.setOnPreferenceChangeListener(this); - mDNS2.setOnPreferenceChangeListener(this); - mUsePull.setOnPreferenceChangeListener(this); - mOverrideDNS.setOnPreferenceChangeListener(this); - mSearchdomain.setOnPreferenceChangeListener(this); - - loadSettings(); - } - - @Override - protected void loadSettings() { - - mUsePull.setChecked(mProfile.mUsePull); - mIPv4.setText(mProfile.mIPv4Address); - mIPv6.setText(mProfile.mIPv6Address); - mDNS1.setText(mProfile.mDNS1); - mDNS2.setText(mProfile.mDNS2); - mOverrideDNS.setChecked(mProfile.mOverrideDNS); - mSearchdomain.setText(mProfile.mSearchDomain); - mNobind.setChecked(mProfile.mNobind); - if (mProfile.mAuthenticationType == VpnProfile.TYPE_STATICKEYS) - mUsePull.setChecked(false); - - // Sets Summary - onPreferenceChange(mIPv4, mIPv4.getText()); - onPreferenceChange(mIPv6, mIPv6.getText()); - onPreferenceChange(mDNS1, mDNS1.getText()); - onPreferenceChange(mDNS2, mDNS2.getText()); - onPreferenceChange(mSearchdomain, mSearchdomain.getText()); - - setDNSState(); - } - - - @Override - protected void saveSettings() { - mProfile.mUsePull = mUsePull.isChecked(); - mProfile.mIPv4Address = mIPv4.getText(); - mProfile.mIPv6Address = mIPv6.getText(); - mProfile.mDNS1 = mDNS1.getText(); - mProfile.mDNS2 = mDNS2.getText(); - mProfile.mOverrideDNS = mOverrideDNS.isChecked(); - mProfile.mSearchDomain = mSearchdomain.getText(); - mProfile.mNobind = mNobind.isChecked(); - - } - - @Override - public boolean onPreferenceChange(Preference preference, - Object newValue) { - if(preference==mIPv4 || preference == mIPv6 - || preference==mDNS1 || preference == mDNS2 - || preference == mSearchdomain - ) - - preference.setSummary((String)newValue); - - if(preference== mUsePull || preference == mOverrideDNS) - if(preference==mOverrideDNS) { - // Set so the function gets the right value - mOverrideDNS.setChecked((Boolean) newValue); - } - setDNSState(); - - saveSettings(); - return true; - } - - private void setDNSState() { - boolean enabled; - mOverrideDNS.setEnabled(mUsePull.isChecked()); - if(!mUsePull.isChecked()) - enabled =true; - else - enabled = mOverrideDNS.isChecked(); - - mDNS1.setEnabled(enabled); - mDNS2.setEnabled(enabled); - mSearchdomain.setEnabled(enabled); - - - } - - - } \ No newline at end of file diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Obscure.java b/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Obscure.java deleted file mode 100644 index 4507d691..00000000 --- a/app/src/main/java/de/blinkt/openvpn/fragments/Settings_Obscure.java +++ /dev/null @@ -1,93 +0,0 @@ -package de.blinkt.openvpn.fragments; - -import android.os.Bundle; -import android.preference.CheckBoxPreference; -import android.preference.EditTextPreference; -import android.preference.ListPreference; -import android.preference.Preference; -import android.preference.Preference.OnPreferenceChangeListener; -import se.leap.bitmaskclient.R; - -public class Settings_Obscure extends OpenVpnPreferencesFragment implements OnPreferenceChangeListener { - private CheckBoxPreference mUseRandomHostName; - private CheckBoxPreference mUseFloat; - private CheckBoxPreference mUseCustomConfig; - private EditTextPreference mCustomConfig; - private ListPreference mLogverbosity; - private CheckBoxPreference mPersistent; - private ListPreference mConnectretrymax; - private EditTextPreference mConnectretry; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - // Load the preferences from an XML resource - addPreferencesFromResource(R.xml.vpn_obscure); - - mUseRandomHostName = (CheckBoxPreference) findPreference("useRandomHostname"); - mUseFloat = (CheckBoxPreference) findPreference("useFloat"); - mUseCustomConfig = (CheckBoxPreference) findPreference("enableCustomOptions"); - mCustomConfig = (EditTextPreference) findPreference("customOptions"); - mPersistent = (CheckBoxPreference) findPreference("usePersistTun"); - mConnectretrymax = (ListPreference) findPreference("connectretrymax"); - mConnectretry = (EditTextPreference) findPreference("connectretry"); - - mConnectretrymax.setOnPreferenceChangeListener(this); - mConnectretrymax.setSummary("%s"); - - mConnectretry.setOnPreferenceChangeListener(this); - - - loadSettings(); - - } - - protected void loadSettings() { - mUseRandomHostName.setChecked(mProfile.mUseRandomHostname); - mUseFloat.setChecked(mProfile.mUseFloat); - mUseCustomConfig.setChecked(mProfile.mUseCustomConfig); - mCustomConfig.setText(mProfile.mCustomConfigOptions); - mPersistent.setChecked(mProfile.mPersistTun); - - mConnectretrymax.setValue(mProfile.mConnectRetryMax); - onPreferenceChange(mConnectretrymax, mProfile.mConnectRetryMax); - - mConnectretry.setText(mProfile.mConnectRetry); - onPreferenceChange(mConnectretry, mProfile.mConnectRetry); - } - - - protected void saveSettings() { - mProfile.mUseRandomHostname = mUseRandomHostName.isChecked(); - mProfile.mUseFloat = mUseFloat.isChecked(); - mProfile.mUseCustomConfig = mUseCustomConfig.isChecked(); - mProfile.mCustomConfigOptions = mCustomConfig.getText(); - mProfile.mConnectRetryMax = mConnectretrymax.getValue(); - mProfile.mPersistTun = mPersistent.isChecked(); - mProfile.mConnectRetry = mConnectretry.getText(); - } - - - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - if (preference == mConnectretrymax) { - if(newValue==null) { - newValue="5"; - } - mConnectretrymax.setDefaultValue(newValue); - - for(int i=0;i supportedMimeTypes = new TreeSet(); - Vector extensions = new Vector(); - - switch (fileType) { - case PKCS12: - i.setType("application/x-pkcs12"); - supportedMimeTypes.add("application/x-pkcs12"); - extensions.add("p12"); - extensions.add("pfx"); - break; - case CLIENT_CERTIFICATE: - case CA_CERTIFICATE: - i.setType("application/x-pem-file"); - supportedMimeTypes.add("application/x-x509-ca-cert"); - supportedMimeTypes.add("application/x-x509-user-cert"); - supportedMimeTypes.add("application/x-pem-file"); - supportedMimeTypes.add("text/plain"); - - extensions.add("pem"); - extensions.add("crt"); - break; - case KEYFILE: - i.setType("application/x-pem-file"); - supportedMimeTypes.add("application/x-pem-file"); - supportedMimeTypes.add("application/pkcs8"); - - // Google drive .... - supportedMimeTypes.add("application/x-iwork-keynote-sffkey"); - extensions.add("key"); - break; - - case TLS_AUTH_FILE: - i.setType("text/plain"); - - // Backup .... - supportedMimeTypes.add("application/pkcs8"); - // Google Drive is kind of crazy ..... - supportedMimeTypes.add("application/x-iwork-keynote-sffkey"); - - extensions.add("txt"); - extensions.add("key"); - break; - - case OVPN_CONFIG: - i.setType("application/x-openvpn-profile"); - supportedMimeTypes.add("application/x-openvpn-profile"); - supportedMimeTypes.add("application/openvpn-profile"); - supportedMimeTypes.add("application/ovpn"); - supportedMimeTypes.add("text/plain"); - extensions.add("ovpn"); - extensions.add("conf"); - break; - - case USERPW_FILE: - i.setType("text/plain"); - supportedMimeTypes.add("text/plain"); - break; - } - - MimeTypeMap mtm = MimeTypeMap.getSingleton(); - - for (String ext : extensions) { - String mimeType = mtm.getMimeTypeFromExtension(ext); - if (mimeType != null) - supportedMimeTypes.add(mimeType); - } - - // Always add this as fallback - supportedMimeTypes.add("application/octet-stream"); - - i.putExtra(Intent.EXTRA_MIME_TYPES, supportedMimeTypes.toArray(new String[supportedMimeTypes.size()])); - - - /* Samsung has decided to do something strange, on stock Android GET_CONTENT opens the document UI */ - /* fist try with documentsui */ - i.setPackage("com.android.documentsui"); - - //noinspection ConstantConditions - if (true || !isIntentAvailable(c,i)) { - i.setAction(Intent.ACTION_OPEN_DOCUMENT); - i.setPackage(null); - } - - return i; - } - - - public static boolean isIntentAvailable(Context context, Intent i) { - final PackageManager packageManager = context.getPackageManager(); - List list = - packageManager.queryIntentActivities(i, - PackageManager.MATCH_DEFAULT_ONLY); - return list.size() > 0; - } - - - public enum FileType { - PKCS12(0), - CLIENT_CERTIFICATE(1), - CA_CERTIFICATE(2), - OVPN_CONFIG(3), - KEYFILE(4), - TLS_AUTH_FILE(5), - USERPW_FILE(6); - - private int value; - - FileType(int i) { - value = i; - } - - public static FileType getFileTypeByValue(int value) { - switch (value) { - case 0: - return PKCS12; - case 1: - return CLIENT_CERTIFICATE; - case 2: - return CA_CERTIFICATE; - case 3: - return OVPN_CONFIG; - case 4: - return KEYFILE; - case 5: - return TLS_AUTH_FILE; - case 6: - return USERPW_FILE; - default: - return null; - } - } - - public int getValue() { - return value; - } - } - - static private byte[] readBytesFromStream(InputStream input) throws IOException { - - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - - int nRead; - byte[] data = new byte[16384]; - - while ((nRead = input.read(data, 0, data.length)) != -1) { - buffer.write(data, 0, nRead); - } - - buffer.flush(); - input.close(); - return buffer.toByteArray(); - } - - public static String getFilePickerResult(FileType ft, Intent result, Context c) throws IOException, SecurityException { - - Uri uri = result.getData(); - if (uri == null) - return null; - - byte[] fileData = readBytesFromStream(c.getContentResolver().openInputStream(uri)); - String newData = null; - - Cursor cursor = c.getContentResolver().query(uri, null, null, null, null); - - String prefix = ""; - try { - if (cursor!=null && cursor.moveToFirst()) { - int cidx = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); - if (cidx != -1) { - String displayName = cursor.getString(cidx); - - if (!displayName.contains(VpnProfile.INLINE_TAG) && !displayName.contains(VpnProfile.DISPLAYNAME_TAG)) - prefix = VpnProfile.DISPLAYNAME_TAG + displayName; - } - } - } finally { - if(cursor!=null) - cursor.close(); - } - - switch (ft) { - case PKCS12: - newData = Base64.encodeToString(fileData, Base64.DEFAULT); - break; - default: - newData = new String(fileData, "UTF-8"); - break; - } - - return prefix + VpnProfile.INLINE_TAG + newData; - } -} diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/VPNProfileList.java b/app/src/main/java/de/blinkt/openvpn/fragments/VPNProfileList.java deleted file mode 100644 index 693a7e71..00000000 --- a/app/src/main/java/de/blinkt/openvpn/fragments/VPNProfileList.java +++ /dev/null @@ -1,346 +0,0 @@ -package de.blinkt.openvpn.fragments; - -import android.annotation.TargetApi; -import android.app.Activity; -import android.app.AlertDialog; -import android.app.ListFragment; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.text.Html; -import android.text.Html.ImageGetter; -import android.view.*; -import android.view.View.OnClickListener; -import android.webkit.MimeTypeMap; -import android.widget.ArrayAdapter; -import android.widget.EditText; -import android.widget.TextView; -import android.widget.Toast; -import de.blinkt.openvpn.*; -import de.blinkt.openvpn.activities.ConfigConverter; -import de.blinkt.openvpn.activities.FileSelect; -import de.blinkt.openvpn.activities.VPNPreferences; -import de.blinkt.openvpn.core.ProfileManager; - -import java.util.Collection; -import java.util.Comparator; -import java.util.TreeSet; - -public class VPNProfileList extends ListFragment { - - public final static int RESULT_VPN_DELETED = Activity.RESULT_FIRST_USER; - - private static final int MENU_ADD_PROFILE = Menu.FIRST; - - private static final int START_VPN_CONFIG = 92; - private static final int SELECT_PROFILE = 43; - private static final int IMPORT_PROFILE = 231; - private static final int FILE_PICKER_RESULT = 392; - - private static final int MENU_IMPORT_PROFILE = Menu.FIRST +1; - - - class VPNArrayAdapter extends ArrayAdapter { - - public VPNArrayAdapter(Context context, int resource, - int textViewResourceId) { - super(context, resource, textViewResourceId); - } - - @Override - public View getView(final int position, View convertView, ViewGroup parent) { - View v = super.getView(position, convertView, parent); - - View titleview = v.findViewById(R.id.vpn_list_item_left); - titleview.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - VpnProfile profile =(VpnProfile) getListAdapter().getItem(position); - startVPN(profile); - } - }); - - View settingsview = v.findViewById(R.id.quickedit_settings); - settingsview.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - VpnProfile editProfile = (VpnProfile) getListAdapter().getItem(position); - editVPN(editProfile); - - } - }); - - return v; - } - } - - - - - - - - - private ArrayAdapter mArrayadapter; - - protected VpnProfile mEditProfile=null; - - - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); - - } - - - class MiniImageGetter implements ImageGetter { - - - @Override - public Drawable getDrawable(String source) { - Drawable d=null; - if ("ic_menu_add".equals(source)) - d = getActivity().getResources().getDrawable(android.R.drawable.ic_menu_add); - else if("ic_menu_archive".equals(source)) - d = getActivity().getResources().getDrawable(R.drawable.ic_menu_archive); - - - - if(d!=null) { - d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); - return d; - }else{ - return null; - } - } - } - - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View v = inflater.inflate(R.layout.vpn_profile_list, container,false); - - TextView newvpntext = (TextView) v.findViewById(R.id.add_new_vpn_hint); - TextView importvpntext = (TextView) v.findViewById(R.id.import_vpn_hint); - - - - newvpntext.setText(Html.fromHtml(getString(R.string.add_new_vpn_hint),new MiniImageGetter(),null)); - importvpntext.setText(Html.fromHtml(getString(R.string.vpn_import_hint),new MiniImageGetter(),null)); - - - - return v; - - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - setListAdapter(); - } - - static class VpnProfileNameComparator implements Comparator { - - @Override - public int compare(VpnProfile lhs, VpnProfile rhs) { - if (lhs == rhs) - // Catches also both null - return 0; - - if (lhs == null) - return -1; - if (rhs == null) - return 1; - - if (lhs.mName == null) - return -1; - if (rhs.mName == null) - return 1; - - return lhs.mName.compareTo(rhs.mName); - } - - } - - private void setListAdapter() { - mArrayadapter = new VPNArrayAdapter(getActivity(),R.layout.vpn_list_item,R.id.vpn_item_title); - Collection allvpn = getPM().getProfiles(); - - TreeSet sortedset = new TreeSet(new VpnProfileNameComparator()); - sortedset.addAll(allvpn); - mArrayadapter.addAll(sortedset); - - setListAdapter(mArrayadapter); - } - - - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - menu.add(0, MENU_ADD_PROFILE, 0 , R.string.menu_add_profile) - .setIcon(android.R.drawable.ic_menu_add) - .setAlphabeticShortcut('a') - .setTitleCondensed(getActivity().getString(R.string.add)) - .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - - menu.add(0, MENU_IMPORT_PROFILE, 0, R.string.menu_import) - .setIcon(R.drawable.ic_menu_archive) - .setAlphabeticShortcut('i') - .setTitleCondensed(getActivity().getString(R.string.menu_import_short)) - .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT ); - } - - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - final int itemId = item.getItemId(); - if (itemId == MENU_ADD_PROFILE) { - onAddProfileClicked(); - return true; - } else if (itemId == MENU_IMPORT_PROFILE) { - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) - startFilePicker(); - else - startImportConfig(); - - return true; - } else { - return super.onOptionsItemSelected(item); - } - } - - @TargetApi(Build.VERSION_CODES.KITKAT) - private void startFilePicker() { - Intent i = Utils.getFilePickerIntent(getActivity(), Utils.FileType.OVPN_CONFIG); - startActivityForResult(i, FILE_PICKER_RESULT); - } - - private void startImportConfig() { - Intent intent = new Intent(getActivity(),FileSelect.class); - intent.putExtra(FileSelect.NO_INLINE_SELECTION, true); - intent.putExtra(FileSelect.WINDOW_TITLE, R.string.import_configuration_file); - startActivityForResult(intent, SELECT_PROFILE); - } - - - - - - private void onAddProfileClicked() { - Context context = getActivity(); - if (context != null) { - final EditText entry = new EditText(context); - entry.setSingleLine(); - - AlertDialog.Builder dialog = new AlertDialog.Builder(context); - dialog.setTitle(R.string.menu_add_profile); - dialog.setMessage(R.string.add_profile_name_prompt); - dialog.setView(entry); - - - dialog.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - String name = entry.getText().toString(); - if (getPM().getProfileByName(name)==null) { - VpnProfile profile = new VpnProfile(name); - addProfile(profile); - editVPN(profile); - } else { - Toast.makeText(getActivity(), R.string.duplicate_profile_name, Toast.LENGTH_LONG).show(); - } - } - - - }); - dialog.setNegativeButton(android.R.string.cancel, null); - dialog.create().show(); - } - - } - - private void addProfile(VpnProfile profile) { - getPM().addProfile(profile); - getPM().saveProfileList(getActivity()); - getPM().saveProfile(getActivity(),profile); - mArrayadapter.add(profile); - } - - private ProfileManager getPM() { - return ProfileManager.getInstance(getActivity()); - } - - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - if(resultCode == RESULT_VPN_DELETED){ - if(mArrayadapter != null && mEditProfile !=null) - mArrayadapter.remove(mEditProfile); - } - - if(resultCode != Activity.RESULT_OK) - return; - - if (requestCode == START_VPN_CONFIG) { - String configuredVPN = data.getStringExtra(VpnProfile.EXTRA_PROFILEUUID); - - VpnProfile profile = ProfileManager.get(getActivity(),configuredVPN); - getPM().saveProfile(getActivity(), profile); - // Name could be modified, reset List adapter - setListAdapter(); - - } else if(requestCode== SELECT_PROFILE) { - String fileData = data.getStringExtra(FileSelect.RESULT_DATA); - Uri uri = new Uri.Builder().path(fileData).scheme("file").build(); - - startConfigImport(uri); - } else if(requestCode == IMPORT_PROFILE) { - String profileUUID = data.getStringExtra(VpnProfile.EXTRA_PROFILEUUID); - mArrayadapter.add(ProfileManager.get(getActivity(), profileUUID)); - } else if(requestCode == FILE_PICKER_RESULT) { - if (data != null) { - Uri uri = data.getData(); - startConfigImport(uri); - } - } - - } - - private void startConfigImport(Uri uri) { - Intent startImport = new Intent(getActivity(),ConfigConverter.class); - startImport.setAction(ConfigConverter.IMPORT_PROFILE); - startImport.setData(uri); - startActivityForResult(startImport, IMPORT_PROFILE); - } - - - private void editVPN(VpnProfile profile) { - mEditProfile =profile; - Intent vprefintent = new Intent(getActivity(),VPNPreferences.class) - .putExtra(getActivity().getPackageName() + ".profileUUID", profile.getUUID().toString()); - - startActivityForResult(vprefintent,START_VPN_CONFIG); - } - - private void startVPN(VpnProfile profile) { - - getPM().saveProfile(getActivity(), profile); - - Intent intent = new Intent(getActivity(),LaunchVPN.class); - intent.putExtra(LaunchVPN.EXTRA_KEY, profile.getUUID().toString()); - intent.setAction(Intent.ACTION_MAIN); - startActivity(intent); - } -} diff --git a/app/src/main/java/de/blinkt/openvpn/views/FileSelectLayout.java b/app/src/main/java/de/blinkt/openvpn/views/FileSelectLayout.java deleted file mode 100644 index 06dbec22..00000000 --- a/app/src/main/java/de/blinkt/openvpn/views/FileSelectLayout.java +++ /dev/null @@ -1,158 +0,0 @@ -package de.blinkt.openvpn.views; - -import se.leap.bitmaskclient.R; -import de.blinkt.openvpn.VpnProfile; -import de.blinkt.openvpn.activities.FileSelect; -import de.blinkt.openvpn.core.VpnStatus; -import de.blinkt.openvpn.core.X509Utils; -import android.content.Context; -import android.content.Intent; -import android.content.res.TypedArray; -import android.util.AttributeSet; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.Button; -import android.widget.LinearLayout; -import android.widget.TextView; -import de.blinkt.openvpn.fragments.Utils; - -import java.io.*; - -import static android.os.Build.*; - - -public class FileSelectLayout extends LinearLayout implements OnClickListener { - - public void parseResponse(Intent data, Context c) { - if (VERSION.SDK_INT < VERSION_CODES.KITKAT) { - String fileData = data.getStringExtra(FileSelect.RESULT_DATA); - setData(fileData, c); - } else if (data != null) { - try { - String newData = Utils.getFilePickerResult(fileType, data, c); - if (newData!=null) - setData(newData, c); - - } catch (IOException e) { - VpnStatus.logException(e); - } catch (SecurityException e) { - VpnStatus.logException(e); - } - - - } - } - - public interface FileSelectCallback { - - String getString(int res); - - void startActivityForResult(Intent startFC, int mTaskId); - } - - private boolean mIsCertificate; - private TextView mDataView; - private String mData; - private FileSelectCallback mFragment; - private int mTaskId; - private Button mSelectButton; - private Utils.FileType fileType; - private String mTitle; - private boolean mShowClear; - private TextView mDataDetails; - - public FileSelectLayout(Context context, AttributeSet attrset) { - super(context, attrset); - - TypedArray ta = context.obtainStyledAttributes(attrset, R.styleable.FileSelectLayout); - - setupViews(ta.getString(R.styleable.FileSelectLayout_title), ta.getBoolean(R.styleable.FileSelectLayout_certificate, true)); - - ta.recycle(); - } - - public FileSelectLayout (Context context, String title, boolean isCerticate) - { - super(context); - - setupViews(title, isCerticate); - - } - - private void setupViews(String title, boolean isCertificate) { - inflate(getContext(), R.layout.file_select, this); - - mTitle = title; - mIsCertificate = isCertificate; - - TextView tview = (TextView) findViewById(R.id.file_title); - tview.setText(mTitle); - - mDataView = (TextView) findViewById(R.id.file_selected_item); - mDataDetails = (TextView) findViewById(R.id.file_selected_description); - mSelectButton = (Button) findViewById(R.id.file_select_button); - mSelectButton.setOnClickListener(this); - } - - - public void setCaller(FileSelectCallback fragment, int i, Utils.FileType ft) { - mTaskId = i; - mFragment = fragment; - fileType = ft; - } - - public void getCertificateFileDialog() { - Intent startFC = new Intent(getContext(), FileSelect.class); - startFC.putExtra(FileSelect.START_DATA, mData); - startFC.putExtra(FileSelect.WINDOW_TITLE, mTitle); - if (fileType == Utils.FileType.PKCS12) - startFC.putExtra(FileSelect.DO_BASE64_ENCODE, true); - if (mShowClear) - startFC.putExtra(FileSelect.SHOW_CLEAR_BUTTON, true); - - mFragment.startActivityForResult(startFC, mTaskId); - } - - - public String getData() { - return mData; - } - - public void setData(String data, Context c) { - mData = data; - if (data == null) { - mDataView.setText(c.getString(R.string.no_data)); - mDataDetails.setText(""); - } else { - if (mData.startsWith(VpnProfile.DISPLAYNAME_TAG)) { - mDataView.setText(c.getString(R.string.imported_from_file, VpnProfile.getDisplayName(mData))); - } else if (mData.startsWith(VpnProfile.INLINE_TAG)) - mDataView.setText(R.string.inline_file_data); - else - mDataView.setText(data); - if (mIsCertificate) - mDataDetails.setText(X509Utils.getCertificateFriendlyName(c, data)); - } - - } - - @Override - public void onClick(View v) { - if (v == mSelectButton) { - if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) { - Intent startFilePicker = Utils.getFilePickerIntent(getContext(),fileType); - mFragment.startActivityForResult(startFilePicker, mTaskId); - } else { - getCertificateFileDialog(); - } - } - } - - - - - public void setShowClear() { - mShowClear = true; - } - -} diff --git a/app/src/main/java/de/blinkt/openvpn/views/RemoteCNPreference.java b/app/src/main/java/de/blinkt/openvpn/views/RemoteCNPreference.java deleted file mode 100644 index b24e6f8f..00000000 --- a/app/src/main/java/de/blinkt/openvpn/views/RemoteCNPreference.java +++ /dev/null @@ -1,141 +0,0 @@ -package de.blinkt.openvpn.views; - -import android.content.Context; -import android.preference.DialogPreference; -import android.util.AttributeSet; -import android.util.Pair; -import android.view.View; -import android.widget.ArrayAdapter; -import android.widget.EditText; -import android.widget.Spinner; -import android.widget.TextView; - -import se.leap.bitmaskclient.R; -import de.blinkt.openvpn.VpnProfile; - -public class RemoteCNPreference extends DialogPreference { - - - private Spinner mSpinner; - private EditText mEditText; - private int mDNType; - private String mDn; - private TextView mRemoteTLSNote; - //private ScrollView mScrollView; - - public RemoteCNPreference(Context context, AttributeSet attrs) { - super(context, attrs); - setDialogLayoutResource(R.layout.tlsremote); - - } - - @Override - protected void onBindDialogView(View view) { - - super.onBindDialogView(view); - - mEditText = (EditText) view.findViewById(R.id.tlsremotecn); - mSpinner = (Spinner) view.findViewById(R.id.x509verifytype); - mRemoteTLSNote = (TextView) view.findViewById(R.id.tlsremotenote); - //mScrollView = (ScrollView) view.findViewById(R.id.tlsremotescroll); - if(mDn!=null) - mEditText.setText(mDn); - - populateSpinner(); - - } - - - - public String getCNText() { - return mDn; - } - - public int getAuthtype() { - return mDNType; - } - - public void setDN(String dn) { - mDn = dn; - if(mEditText!=null) - mEditText.setText(dn); - } - - public void setAuthType(int x509authtype) { - mDNType = x509authtype; - if (mSpinner!=null) - populateSpinner(); - } - - @Override - protected void onDialogClosed(boolean positiveResult) { - super.onDialogClosed(positiveResult); - - if (positiveResult) { - String dn = mEditText.getText().toString(); - int authtype = getAuthTypeFromSpinner(); - if (callChangeListener(new Pair(authtype, dn))) { - mDn = dn; - mDNType = authtype; - } - } - } - - private void populateSpinner() { - ArrayAdapter authtypes = new ArrayAdapter(getContext(), android.R.layout.simple_spinner_item); - authtypes.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - - authtypes.add(getContext().getString(R.string.complete_dn)); - authtypes.add(getContext().getString(R.string.rdn)); - authtypes.add(getContext().getString(R.string.rdn_prefix)); - if ((mDNType == VpnProfile.X509_VERIFY_TLSREMOTE || mDNType == VpnProfile.X509_VERIFY_TLSREMOTE_COMPAT_NOREMAPPING) - && !(mDn==null || "".equals(mDn))) { - authtypes.add(getContext().getString(R.string.tls_remote_deprecated)); - mRemoteTLSNote.setVisibility(View.VISIBLE); - } else { - mRemoteTLSNote.setVisibility(View.GONE); - } - mSpinner.setAdapter(authtypes); - mSpinner.setSelection(getSpinnerPositionFromAuthTYPE()); - } - - private int getSpinnerPositionFromAuthTYPE() { - switch (mDNType) { - case VpnProfile.X509_VERIFY_TLSREMOTE_DN: - return 0; - case VpnProfile.X509_VERIFY_TLSREMOTE_RDN: - return 1; - case VpnProfile.X509_VERIFY_TLSREMOTE_RDN_PREFIX: - return 2; - case VpnProfile.X509_VERIFY_TLSREMOTE_COMPAT_NOREMAPPING: - case VpnProfile.X509_VERIFY_TLSREMOTE: - if (mDn==null || "".equals(mDn)) - return 1; - else - return 3; - - - default: - return 0; - } - } - - private int getAuthTypeFromSpinner() { - int pos = mSpinner.getSelectedItemPosition(); - switch (pos) { - case 0: - return VpnProfile.X509_VERIFY_TLSREMOTE_DN; - case 1: - return VpnProfile.X509_VERIFY_TLSREMOTE_RDN; - case 2: - return VpnProfile.X509_VERIFY_TLSREMOTE_RDN_PREFIX; - case 3: - // This is the tls-remote entry, only visible if mDntype is a - // tls-remote type - return mDNType; - default: - return VpnProfile.X509_VERIFY_TLSREMOTE; - } - } - -} diff --git a/app/src/main/java/de/blinkt/openvpn/views/SeekBarTicks.java b/app/src/main/java/de/blinkt/openvpn/views/SeekBarTicks.java deleted file mode 100644 index 88e8e164..00000000 --- a/app/src/main/java/de/blinkt/openvpn/views/SeekBarTicks.java +++ /dev/null @@ -1,69 +0,0 @@ -package de.blinkt.openvpn.views; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.ViewConfiguration; -import android.widget.SeekBar; - -public class SeekBarTicks extends SeekBar { - private Paint mTickPaint; - private float mTickHeight; - - private float tickHeightRatio = 0.6f; - - public SeekBarTicks(Context context, AttributeSet attrs) { - super (context, attrs); - - initTicks (context, attrs, android.R.attr.seekBarStyle); - } - - - public SeekBarTicks(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - initTicks (context, attrs, defStyle); - - /*mTickHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - tickHeightDP, - ctx.getResources().getDisplayMetrics()); */ - } - - private void initTicks(Context context, AttributeSet attrs, int defStyle) { - TypedArray a = context.obtainStyledAttributes(attrs, - new int[] { android.R.attr.secondaryProgress }, defStyle, 0); - - - int tickColor = a.getColor(0, android.R.color.black); - mTickPaint = new Paint(); - mTickPaint.setColor( context.getResources().getColor(tickColor)); - a.recycle(); - } - - - @Override - protected synchronized void onDraw(Canvas canvas) { - drawTicks(canvas); - super.onDraw(canvas); - } - - private void drawTicks(Canvas canvas) { - - final int available = getWidth() - getPaddingLeft() - getPaddingRight(); - final int availableHeight = getHeight() - getPaddingBottom() - getPaddingTop(); - - int extrapadding = (int) ((availableHeight- (availableHeight * tickHeightRatio))/2); - - int tickSpacing = available / (getMax() ); - - for (int i = 1; i < getMax(); i++) { - final float x = getPaddingLeft() + i * tickSpacing; - - canvas.drawLine(x, getPaddingTop()+extrapadding, x, getHeight()-getPaddingBottom()-extrapadding, mTickPaint); - } - } -} -- cgit v1.2.3 From 6fcd101fcbc7779ffd7239cc35e5c3359ae38fcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parm=C3=A9nides=20GV?= Date: Wed, 28 May 2014 17:15:44 +0200 Subject: Resources merge correctly. --- .../main/java/de/blinkt/openvpn/FileProvider.java | 142 --------------------- app/src/main/java/de/blinkt/openvpn/LaunchVPN.java | 72 +---------- .../java/de/blinkt/openvpn/OnBootReceiver.java | 36 ------ .../main/java/de/blinkt/openvpn/VpnProfile.java | 2 + .../de/blinkt/openvpn/fragments/LogFragment.java | 19 +-- 5 files changed, 10 insertions(+), 261 deletions(-) delete mode 100644 app/src/main/java/de/blinkt/openvpn/FileProvider.java delete mode 100644 app/src/main/java/de/blinkt/openvpn/OnBootReceiver.java (limited to 'app/src/main/java/de/blinkt/openvpn') diff --git a/app/src/main/java/de/blinkt/openvpn/FileProvider.java b/app/src/main/java/de/blinkt/openvpn/FileProvider.java deleted file mode 100644 index f9fedd02..00000000 --- a/app/src/main/java/de/blinkt/openvpn/FileProvider.java +++ /dev/null @@ -1,142 +0,0 @@ -package de.blinkt.openvpn; - -import se.leap.bitmaskclient.R; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; - -import android.content.ContentProvider; -import android.content.ContentProvider.PipeDataWriter; -import android.content.ContentValues; -import android.content.res.AssetFileDescriptor; -import android.database.Cursor; -import android.database.MatrixCursor; -import android.net.Uri; -import android.os.Bundle; -import android.os.ParcelFileDescriptor; -import android.provider.OpenableColumns; -import android.util.Log; -import de.blinkt.openvpn.core.VpnStatus; - -/** - * A very simple content provider that can serve arbitrary asset files from - * our .apk. - */ -public class FileProvider extends ContentProvider -implements PipeDataWriter { - @Override - public boolean onCreate() { - return true; - } - - @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, - String sortOrder) { - try { - File dumpfile = getFileFromURI(uri); - - - MatrixCursor c = new MatrixCursor(projection); - - Object[] row = new Object[projection.length]; - int i=0; - for (String r:projection) { - if(r.equals(OpenableColumns.SIZE)) - row[i] = dumpfile.length(); - if(r.equals(OpenableColumns.DISPLAY_NAME)) - row[i] = dumpfile.getName(); - i++; - } - c.addRow(row); - return c; - } catch (FileNotFoundException e) { - VpnStatus.logException(e); - return null; - } - - - } - - @Override - public Uri insert(Uri uri, ContentValues values) { - // Don't support inserts. - return null; - } - - @Override - public int delete(Uri uri, String selection, String[] selectionArgs) { - // Don't support deletes. - return 0; - } - - @Override - public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - // Don't support updates. - return 0; - } - - @Override - public String getType(Uri uri) { - // For this sample, assume all files are .apks. - return "application/octet-stream"; - } - - @Override - public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException { - File dumpfile = getFileFromURI(uri); - - try { - - InputStream is = new FileInputStream(dumpfile); - // Start a new thread that pipes the stream data back to the caller. - return new AssetFileDescriptor( - openPipeHelper(uri, null, null, is, this), 0, - dumpfile.length()); - } catch (IOException e) { - throw new FileNotFoundException("Unable to open minidump " + uri); - } - } - - private File getFileFromURI(Uri uri) throws FileNotFoundException { - // Try to open an asset with the given name. - String path = uri.getPath(); - if(path.startsWith("/")) - path = path.replaceFirst("/", ""); - - // I think this already random enough, no need for magic secure cookies - // 1f9563a4-a1f5-2165-255f2219-111823ef.dmp - if (!path.matches("^[0-9a-z-.]*(dmp|dmp.log)$")) - throw new FileNotFoundException("url not in expect format " + uri); - File cachedir = getContext().getCacheDir(); - return new File(cachedir,path); - } - - @Override - public void writeDataToPipe(ParcelFileDescriptor output, Uri uri, String mimeType, - Bundle opts, InputStream args) { - // Transfer data from the asset to the pipe the client is reading. - byte[] buffer = new byte[8192]; - int n; - FileOutputStream fout = new FileOutputStream(output.getFileDescriptor()); - try { - while ((n=args.read(buffer)) >= 0) { - fout.write(buffer, 0, n); - } - } catch (IOException e) { - Log.i("OpenVPNFileProvider", "Failed transferring", e); - } finally { - try { - args.close(); - } catch (IOException e) { - } - try { - fout.close(); - } catch (IOException e) { - } - } - } -} diff --git a/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java b/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java index cfd0efb0..6b4addb3 100644 --- a/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java +++ b/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java @@ -2,6 +2,8 @@ package de.blinkt.openvpn; import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.R; + import java.io.IOException; import android.app.Activity; @@ -108,75 +110,7 @@ public class LaunchVPN extends Activity { } } - - private void askForPW(final int type) { - - final EditText entry = new EditText(this); - final View userpwlayout = getLayoutInflater().inflate(R.layout.userpass, null); - - entry.setSingleLine(); - entry.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); - entry.setTransformationMethod(new PasswordTransformationMethod()); - - AlertDialog.Builder dialog = new AlertDialog.Builder(this); - dialog.setTitle("Need " + getString(type)); - dialog.setMessage("Enter the password for profile " + mSelectedProfile.mName); - - if (type == R.string.password) { - ((EditText)userpwlayout.findViewById(R.id.username)).setText(mSelectedProfile.mUsername); - ((EditText)userpwlayout.findViewById(R.id.password)).setText(mSelectedProfile.mPassword); - ((CheckBox)userpwlayout.findViewById(R.id.save_password)).setChecked(!TextUtils.isEmpty(mSelectedProfile.mPassword)); - ((CheckBox)userpwlayout.findViewById(R.id.show_password)).setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (isChecked) - ((EditText)userpwlayout.findViewById(R.id.password)).setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); - else - ((EditText)userpwlayout.findViewById(R.id.password)).setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); - } - }); - - dialog.setView(userpwlayout); - } else { - dialog.setView(entry); - } - - AlertDialog.Builder builder = dialog.setPositiveButton(android.R.string.ok, - new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - - if (type == R.string.password) { - mSelectedProfile.mUsername = ((EditText) userpwlayout.findViewById(R.id.username)).getText().toString(); - - String pw = ((EditText) userpwlayout.findViewById(R.id.password)).getText().toString(); - if (((CheckBox) userpwlayout.findViewById(R.id.save_password)).isChecked()) { - mSelectedProfile.mPassword=pw; - } else { - mSelectedProfile.mPassword=null; - mSelectedProfile.mTransientPW = pw; - } - } else { - mSelectedProfile.mTransientPCKS12PW = entry.getText().toString(); - } - onActivityResult(START_VPN_PROFILE, Activity.RESULT_OK, null); - - } - - }); - dialog.setNegativeButton(android.R.string.cancel, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - VpnStatus.updateStateString("USER_VPN_PASSWORD_CANCELLED", "", R.string.state_user_vpn_password_cancelled, - ConnectionStatus.LEVEL_NOTCONNECTED); - finish(); - } - }); - - dialog.create().show(); - - } + @Override protected void onActivityResult (int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); diff --git a/app/src/main/java/de/blinkt/openvpn/OnBootReceiver.java b/app/src/main/java/de/blinkt/openvpn/OnBootReceiver.java deleted file mode 100644 index 129d8d1c..00000000 --- a/app/src/main/java/de/blinkt/openvpn/OnBootReceiver.java +++ /dev/null @@ -1,36 +0,0 @@ -package de.blinkt.openvpn; - -import se.leap.bitmaskclient.R; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import de.blinkt.openvpn.core.ProfileManager; - - -public class OnBootReceiver extends BroadcastReceiver { - - // Debug: am broadcast -a android.intent.action.BOOT_COMPLETED - @Override - public void onReceive(Context context, Intent intent) { - - final String action = intent.getAction(); - - if(Intent.ACTION_BOOT_COMPLETED.equals(action)) { - VpnProfile bootProfile = ProfileManager.getOnBootProfile(context); - if(bootProfile != null) { - launchVPN(bootProfile, context); - } - } - } - - void launchVPN(VpnProfile profile, Context context) { - Intent startVpnIntent = new Intent(Intent.ACTION_MAIN); - startVpnIntent.setClass(context, LaunchVPN.class); - startVpnIntent.putExtra(LaunchVPN.EXTRA_KEY,profile.getUUIDString()); - startVpnIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startVpnIntent.putExtra(LaunchVPN.EXTRA_HIDELOG, true); - - context.startActivity(startVpnIntent); - } -} diff --git a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java index afa70100..000fc47c 100644 --- a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java +++ b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java @@ -2,6 +2,8 @@ package de.blinkt.openvpn; import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.R; + import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; 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 fe7a8bd3..2f04d235 100644 --- a/app/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java +++ b/app/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java @@ -2,6 +2,8 @@ package de.blinkt.openvpn.fragments; import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.R; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; @@ -22,8 +24,7 @@ import android.widget.*; import android.widget.AdapterView.OnItemLongClickListener; import de.blinkt.openvpn.*; import de.blinkt.openvpn.activities.DisconnectVPN; -import de.blinkt.openvpn.activities.MainActivity; -import de.blinkt.openvpn.activities.VPNPreferences; +import se.leap.bitmaskclient.Dashboard; import de.blinkt.openvpn.core.OpenVPNManagement; import de.blinkt.openvpn.core.VpnStatus; import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus; @@ -424,24 +425,14 @@ 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(),VPNPreferences.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) { // This is called when the Home (Up) button is pressed // in the Action Bar. - Intent parentActivityIntent = new Intent(getActivity(), MainActivity.class); + Intent parentActivityIntent = new Intent(getActivity(), Dashboard.class); parentActivityIntent.addFlags( Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); -- cgit v1.2.3 From aba5a16a97b43f95659e0f79f76549b1fe2e9d56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parm=C3=A9nides=20GV?= Date: Thu, 29 May 2014 10:05:47 +0200 Subject: No AIDL nor api, logfragment related xml files api folder and aidl files aren't needed for us. They are used for external apps that want to use ics-openvpn as an already installed package, while what we want is to avoid the installation of ics-openvpn including it (refactored) in bitmask android. --- app/src/main/java/de/blinkt/openvpn/LaunchVPN.java | 19 +- .../java/de/blinkt/openvpn/api/APIVpnProfile.java | 51 ---- .../java/de/blinkt/openvpn/api/ConfirmDialog.java | 126 -------- .../de/blinkt/openvpn/api/ExternalAppDatabase.java | 57 ---- .../blinkt/openvpn/api/ExternalOpenVPNService.java | 317 --------------------- .../openvpn/api/SecurityRemoteException.java | 12 - 6 files changed, 6 insertions(+), 576 deletions(-) delete mode 100644 app/src/main/java/de/blinkt/openvpn/api/APIVpnProfile.java delete mode 100644 app/src/main/java/de/blinkt/openvpn/api/ConfirmDialog.java delete mode 100644 app/src/main/java/de/blinkt/openvpn/api/ExternalAppDatabase.java delete mode 100644 app/src/main/java/de/blinkt/openvpn/api/ExternalOpenVPNService.java delete mode 100644 app/src/main/java/de/blinkt/openvpn/api/SecurityRemoteException.java (limited to 'app/src/main/java/de/blinkt/openvpn') diff --git a/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java b/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java index 6b4addb3..f8487891 100644 --- a/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java +++ b/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java @@ -117,19 +117,12 @@ public class LaunchVPN extends Activity { if(requestCode==START_VPN_PROFILE) { if(resultCode == Activity.RESULT_OK) { - int needpw = mSelectedProfile.needUserPWInput(); - if(needpw !=0) { - VpnStatus.updateStateString("USER_VPN_PASSWORD", "", R.string.state_user_vpn_password, - ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT); - askForPW(needpw); - } else { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - boolean showlogwindow = prefs.getBoolean("showlogwindow", true); - - if(!mhideLog && showlogwindow) - showLogWindow(); - new startOpenVpnThread().start(); - } + 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, diff --git a/app/src/main/java/de/blinkt/openvpn/api/APIVpnProfile.java b/app/src/main/java/de/blinkt/openvpn/api/APIVpnProfile.java deleted file mode 100644 index a44891ab..00000000 --- a/app/src/main/java/de/blinkt/openvpn/api/APIVpnProfile.java +++ /dev/null @@ -1,51 +0,0 @@ -package de.blinkt.openvpn.api; - -import android.os.Parcel; -import android.os.Parcelable; - -public class APIVpnProfile implements Parcelable { - - public final String mUUID; - public final String mName; - public final boolean mUserEditable; - - public APIVpnProfile(Parcel in) { - mUUID = in.readString(); - mName = in.readString(); - mUserEditable = in.readInt() != 0; - } - - public APIVpnProfile(String uuidString, String name, boolean userEditable) { - mUUID=uuidString; - mName = name; - mUserEditable=userEditable; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(mUUID); - dest.writeString(mName); - if(mUserEditable) - dest.writeInt(0); - else - dest.writeInt(1); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - public APIVpnProfile createFromParcel(Parcel in) { - return new APIVpnProfile(in); - } - - public APIVpnProfile[] newArray(int size) { - return new APIVpnProfile[size]; - } - }; - - -} diff --git a/app/src/main/java/de/blinkt/openvpn/api/ConfirmDialog.java b/app/src/main/java/de/blinkt/openvpn/api/ConfirmDialog.java deleted file mode 100644 index 3856a181..00000000 --- a/app/src/main/java/de/blinkt/openvpn/api/ConfirmDialog.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package de.blinkt.openvpn.api; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.AlertDialog.Builder; -import android.content.DialogInterface; -import android.content.DialogInterface.OnShowListener; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.util.Log; -import android.view.View; -import android.widget.Button; -import android.widget.CompoundButton; -import android.widget.ImageView; -import android.widget.TextView; -import se.leap.bitmaskclient.R; - - -public class ConfirmDialog extends Activity implements -CompoundButton.OnCheckedChangeListener, DialogInterface.OnClickListener { - private static final String TAG = "OpenVPNVpnConfirm"; - - private String mPackage; - - private Button mButton; - - private AlertDialog mAlert; - - @Override - protected void onResume() { - super.onResume(); - try { - mPackage = getCallingPackage(); - if (mPackage==null) { - finish(); - return; - } - - - PackageManager pm = getPackageManager(); - ApplicationInfo app = pm.getApplicationInfo(mPackage, 0); - - View view = View.inflate(this, R.layout.api_confirm, null); - ((ImageView) view.findViewById(R.id.icon)).setImageDrawable(app.loadIcon(pm)); - ((TextView) view.findViewById(R.id.prompt)).setText( - getString(R.string.prompt, app.loadLabel(pm), getString(R.string.app))); - ((CompoundButton) view.findViewById(R.id.check)).setOnCheckedChangeListener(this); - - - Builder builder = new AlertDialog.Builder(this); - - builder.setView(view); - - builder.setIconAttribute(android.R.attr.alertDialogIcon); - builder.setTitle(android.R.string.dialog_alert_title); - builder.setPositiveButton(android.R.string.ok,this); - builder.setNegativeButton(android.R.string.cancel,this); - - mAlert = builder.create(); - mAlert.setCanceledOnTouchOutside(false); - - mAlert.setOnShowListener (new OnShowListener() { - - @Override - public void onShow(DialogInterface dialog) { - mButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE); - mButton.setEnabled(false); - - } - }); - - //setCloseOnTouchOutside(false); - - mAlert.show(); - - } catch (Exception e) { - Log.e(TAG, "onResume", e); - finish(); - } - } - - @Override - public void onBackPressed() { - setResult(RESULT_CANCELED); - finish(); - } - - @Override - public void onCheckedChanged(CompoundButton button, boolean checked) { - mButton.setEnabled(checked); - } - - @Override - public void onClick(DialogInterface dialog, int which) { - - if (which == DialogInterface.BUTTON_POSITIVE) { - ExternalAppDatabase extapps = new ExternalAppDatabase(this); - extapps.addApp(mPackage); - setResult(RESULT_OK); - finish(); - } - - if (which == DialogInterface.BUTTON_NEGATIVE) { - setResult(RESULT_CANCELED); - finish(); - } - } - -} - diff --git a/app/src/main/java/de/blinkt/openvpn/api/ExternalAppDatabase.java b/app/src/main/java/de/blinkt/openvpn/api/ExternalAppDatabase.java deleted file mode 100644 index 02c369b1..00000000 --- a/app/src/main/java/de/blinkt/openvpn/api/ExternalAppDatabase.java +++ /dev/null @@ -1,57 +0,0 @@ -package de.blinkt.openvpn.api; - -import java.util.HashSet; -import java.util.Set; - -import android.content.Context; -import android.content.SharedPreferences; -import android.content.SharedPreferences.Editor; -import android.preference.PreferenceManager; - -public class ExternalAppDatabase { - - Context mContext; - - public ExternalAppDatabase(Context c) { - mContext =c; - } - - private final String PREFERENCES_KEY = "PREFERENCES_KEY"; - - boolean isAllowed(String packagename) { - Set allowedapps = getExtAppList(); - - return allowedapps.contains(packagename); - - } - - public Set getExtAppList() { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext); - return prefs.getStringSet(PREFERENCES_KEY, new HashSet()); - } - - void addApp(String packagename) - { - Set allowedapps = getExtAppList(); - allowedapps.add(packagename); - saveExtAppList(allowedapps); - } - - private void saveExtAppList( Set allowedapps) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext); - Editor prefedit = prefs.edit(); - prefedit.putStringSet(PREFERENCES_KEY, allowedapps); - prefedit.apply(); - } - - public void clearAllApiApps() { - saveExtAppList(new HashSet()); - } - - public void removeApp(String packagename) { - Set allowedapps = getExtAppList(); - allowedapps.remove(packagename); - saveExtAppList(allowedapps); - } - -} diff --git a/app/src/main/java/de/blinkt/openvpn/api/ExternalOpenVPNService.java b/app/src/main/java/de/blinkt/openvpn/api/ExternalOpenVPNService.java deleted file mode 100644 index 784ec72a..00000000 --- a/app/src/main/java/de/blinkt/openvpn/api/ExternalOpenVPNService.java +++ /dev/null @@ -1,317 +0,0 @@ -package de.blinkt.openvpn.api; - -import java.io.IOException; -import java.io.StringReader; -import java.lang.ref.WeakReference; -import java.util.LinkedList; -import java.util.List; - -import android.annotation.TargetApi; -import android.app.Service; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.net.VpnService; -import android.os.*; -import se.leap.bitmaskclient.R; -import de.blinkt.openvpn.VpnProfile; -import de.blinkt.openvpn.core.ConfigParser; -import de.blinkt.openvpn.core.ConfigParser.ConfigParseError; -import de.blinkt.openvpn.core.VpnStatus; -import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus; -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 de.blinkt.openvpn.core.VPNLaunchHelper; - -@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) -public class ExternalOpenVPNService extends Service implements StateListener { - - private static final int SEND_TOALL = 0; - - final RemoteCallbackList mCallbacks = - new RemoteCallbackList(); - - private OpenVpnService mService; - private ExternalAppDatabase mExtAppDb; - - - 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; - } - - }; - - @Override - public void onCreate() { - super.onCreate(); - VpnStatus.addStateListener(this); - mExtAppDb = new ExternalAppDatabase(this); - - Intent intent = new Intent(getBaseContext(), OpenVpnService.class); - intent.setAction(OpenVpnService.START_SERVICE); - - bindService(intent, mConnection, Context.BIND_AUTO_CREATE); - mHandler.setService(this); - } - - private final IOpenVPNAPIService.Stub mBinder = new IOpenVPNAPIService.Stub() { - - private void checkOpenVPNPermission() throws SecurityRemoteException { - PackageManager pm = getPackageManager(); - - for (String apppackage : mExtAppDb.getExtAppList()) { - ApplicationInfo app; - try { - app = pm.getApplicationInfo(apppackage, 0); - if (Binder.getCallingUid() == app.uid) { - return; - } - } catch (NameNotFoundException e) { - // App not found. Remove it from the list - mExtAppDb.removeApp(apppackage); - } - - } - throw new SecurityException("Unauthorized OpenVPN API Caller"); - } - - @Override - public List getProfiles() throws RemoteException { - checkOpenVPNPermission(); - - ProfileManager pm = ProfileManager.getInstance(getBaseContext()); - - List profiles = new LinkedList(); - - for (VpnProfile vp : pm.getProfiles()) - profiles.add(new APIVpnProfile(vp.getUUIDString(), vp.mName, vp.mUserEditable)); - - return profiles; - } - - @Override - public void startProfile(String profileUUID) throws RemoteException { - checkOpenVPNPermission(); - - Intent shortVPNIntent = new Intent(Intent.ACTION_MAIN); - shortVPNIntent.setClass(getBaseContext(), de.blinkt.openvpn.LaunchVPN.class); - shortVPNIntent.putExtra(de.blinkt.openvpn.LaunchVPN.EXTRA_KEY, profileUUID); - shortVPNIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(shortVPNIntent); - } - - public void startVPN(String inlineconfig) throws RemoteException { - checkOpenVPNPermission(); - - ConfigParser cp = new ConfigParser(); - try { - cp.parseConfig(new StringReader(inlineconfig)); - VpnProfile vp = cp.convertProfile(); - if (vp.checkProfile(getApplicationContext()) != R.string.no_error_found) - throw new RemoteException(getString(vp.checkProfile(getApplicationContext()))); - - - ProfileManager.setTemporaryProfile(vp); - VPNLaunchHelper.startOpenVpn(vp, getBaseContext()); - - - } catch (IOException e) { - throw new RemoteException(e.getMessage()); - } catch (ConfigParseError e) { - throw new RemoteException(e.getMessage()); - } - } - - @Override - public boolean addVPNProfile(String name, String config) throws RemoteException { - checkOpenVPNPermission(); - - ConfigParser cp = new ConfigParser(); - try { - cp.parseConfig(new StringReader(config)); - VpnProfile vp = cp.convertProfile(); - vp.mName = name; - ProfileManager pm = ProfileManager.getInstance(getBaseContext()); - pm.addProfile(vp); - } catch (IOException e) { - VpnStatus.logException(e); - return false; - } catch (ConfigParseError e) { - VpnStatus.logException(e); - return false; - } - - return true; - } - - - @Override - public Intent prepare(String packagename) { - if (new ExternalAppDatabase(ExternalOpenVPNService.this).isAllowed(packagename)) - return null; - - Intent intent = new Intent(); - intent.setClass(ExternalOpenVPNService.this, ConfirmDialog.class); - return intent; - } - - @Override - public Intent prepareVPNService() throws RemoteException { - checkOpenVPNPermission(); - - if (VpnService.prepare(ExternalOpenVPNService.this) == null) - return null; - else - return new Intent(getBaseContext(), GrantPermissionsActivity.class); - } - - - @Override - public void registerStatusCallback(IOpenVPNStatusCallback cb) - throws RemoteException { - checkOpenVPNPermission(); - - if (cb != null) { - cb.newStatus(mMostRecentState.vpnUUID, mMostRecentState.state, - mMostRecentState.logmessage, mMostRecentState.level.name()); - mCallbacks.register(cb); - } - - - } - - @Override - public void unregisterStatusCallback(IOpenVPNStatusCallback cb) - throws RemoteException { - checkOpenVPNPermission(); - - if (cb != null) - mCallbacks.unregister(cb); - } - - @Override - public void disconnect() throws RemoteException { - checkOpenVPNPermission(); - if (mService != null && mService.getManagement() != null) - mService.getManagement().stopVPN(); - } - - @Override - public void pause() throws RemoteException { - checkOpenVPNPermission(); - if (mService != null) - mService.userPause(true); - } - - @Override - public void resume() throws RemoteException { - checkOpenVPNPermission(); - if (mService != null) - mService.userPause(false); - - } - }; - - - private UpdateMessage mMostRecentState; - - @Override - public IBinder onBind(Intent intent) { - return mBinder; - } - - @Override - public void onDestroy() { - super.onDestroy(); - mCallbacks.kill(); - unbindService(mConnection); - VpnStatus.removeStateListener(this); - } - - class UpdateMessage { - public String state; - public String logmessage; - public ConnectionStatus level; - public String vpnUUID; - - public UpdateMessage(String state, String logmessage, ConnectionStatus level) { - this.state = state; - this.logmessage = logmessage; - this.level = level; - } - } - - @Override - public void updateState(String state, String logmessage, int resid, ConnectionStatus level) { - mMostRecentState = new UpdateMessage(state, logmessage, level); - if (ProfileManager.getLastConnectedVpn() != null) - mMostRecentState.vpnUUID = ProfileManager.getLastConnectedVpn().getUUIDString(); - - Message msg = mHandler.obtainMessage(SEND_TOALL, mMostRecentState); - msg.sendToTarget(); - - } - - private static final OpenVPNServiceHandler mHandler = new OpenVPNServiceHandler(); - - - static class OpenVPNServiceHandler extends Handler { - WeakReference service = null; - - private void setService(ExternalOpenVPNService eos) { - service = new WeakReference(eos); - } - - @Override - public void handleMessage(Message msg) { - - RemoteCallbackList callbacks; - switch (msg.what) { - case SEND_TOALL: - if (service == null || service.get() == null) - return; - - callbacks = service.get().mCallbacks; - - - // Broadcast to all clients the new value. - final int N = callbacks.beginBroadcast(); - for (int i = 0; i < N; i++) { - try { - sendUpdate(callbacks.getBroadcastItem(i), (UpdateMessage) msg.obj); - } catch (RemoteException e) { - // The RemoteCallbackList will take care of removing - // the dead object for us. - } - } - callbacks.finishBroadcast(); - break; - } - } - - private void sendUpdate(IOpenVPNStatusCallback broadcastItem, - UpdateMessage um) throws RemoteException { - broadcastItem.newStatus(um.vpnUUID, um.state, um.logmessage, um.level.name()); - } - } - - -} \ No newline at end of file diff --git a/app/src/main/java/de/blinkt/openvpn/api/SecurityRemoteException.java b/app/src/main/java/de/blinkt/openvpn/api/SecurityRemoteException.java deleted file mode 100644 index 08d90e3b..00000000 --- a/app/src/main/java/de/blinkt/openvpn/api/SecurityRemoteException.java +++ /dev/null @@ -1,12 +0,0 @@ -package de.blinkt.openvpn.api; - -import android.os.RemoteException; - -public class SecurityRemoteException extends RemoteException { - - /** - * - */ - private static final long serialVersionUID = 1L; - -} -- cgit v1.2.3 From e50339824809da28beabfaf9ecea7414c131cbfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parm=C3=A9nides=20GV?= Date: Thu, 29 May 2014 20:19:24 +0200 Subject: ics-openvpn receives ifconfig from the server. Refactored AndroidManifest to include launchvpn and openvpnservice. Imported changes from ics-openvpn-upstream --- app/src/main/java/de/blinkt/openvpn/VpnProfile.java | 4 ++-- app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java | 8 ++++++++ app/src/main/java/de/blinkt/openvpn/core/OpenVpnService.java | 8 ++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) (limited to 'app/src/main/java/de/blinkt/openvpn') diff --git a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java index 000fc47c..da0298f2 100644 --- a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java +++ b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java @@ -727,8 +727,8 @@ public class VpnProfile implements Serializable { //! Return an error if somethign is wrong public int checkProfile(Context context) { if (mAuthenticationType == TYPE_KEYSTORE || mAuthenticationType == TYPE_USERPASS_KEYSTORE) { - if (mAlias == null) - return R.string.no_keystore_cert_selected; + // if (mAlias == null) + // return R.string.no_keystore_cert_selected; } if (!mUsePull || mAuthenticationType == TYPE_STATICKEYS) { 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 378b6b92..32e5cabb 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java +++ b/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java @@ -26,6 +26,14 @@ 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>> 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 8281aed8..d7bb827e 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVpnService.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVpnService.java @@ -72,6 +72,14 @@ 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) -- cgit v1.2.3 From ac69881af1b7bfcdd185989f3e434556b1d62fed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parm=C3=A9nides=20GV?= Date: Tue, 10 Jun 2014 11:19:48 +0200 Subject: Grabs eip authentication data correctly. Merged updated ics-openvpn-upstream (e7803cc8efcd1794e18b4e30a43d814c2834552d). --- .../main/java/de/blinkt/openvpn/VpnProfile.java | 27 ++++++--- .../java/de/blinkt/openvpn/views/SeekBarTicks.java | 69 ++++++++++++++++++++++ 2 files changed, 87 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/de/blinkt/openvpn/views/SeekBarTicks.java (limited to 'app/src/main/java/de/blinkt/openvpn') diff --git a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java index da0298f2..93d0d386 100644 --- a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java +++ b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java @@ -4,6 +4,10 @@ import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.Dashboard; +import se.leap.bitmaskclient.EIP; +import se.leap.bitmaskclient.Provider; + import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -81,7 +85,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_KEYSTORE; + public int mAuthenticationType = TYPE_CERTIFICATES; public String mName; public String mAlias; public String mClientCertFilename; @@ -256,17 +260,22 @@ public class VpnProfile implements Serializable { cfg += " tcp-client\n"; + android.util.Log.d("vpnprofile", Integer.toString(mAuthenticationType)); switch (mAuthenticationType) { case VpnProfile.TYPE_USERPASS_CERTIFICATES: cfg += "auth-user-pass\n"; case VpnProfile.TYPE_CERTIFICATES: // Ca - cfg += insertFileData("ca", mCaFilename); - - // Client Cert + Key - cfg += insertFileData("key", mClientKeyFilename); - cfg += insertFileData("cert", mClientCertFilename); - + // cfg += insertFileData("ca", mCaFilename); + + // // Client Cert + Key + // cfg += insertFileData("key", mClientKeyFilename); + // cfg += insertFileData("cert", mClientCertFilename); + // FIXME This is all we need...The whole switch statement can go... + SharedPreferences preferences = context.getSharedPreferences(Dashboard.SHARED_PREFERENCES, context.MODE_PRIVATE); + cfg+="\n"+preferences.getString(Provider.CA_CERT, "")+"\n\n"; + cfg+="\n"+preferences.getString(EIP.PRIVATE_KEY, "")+"\n\n"; + cfg+="\n"+preferences.getString(EIP.CERTIFICATE, "")+"\n\n"; break; case VpnProfile.TYPE_USERPASS_PKCS12: cfg += "auth-user-pass\n"; @@ -557,8 +566,8 @@ public class VpnProfile implements Serializable { Intent intent = new Intent(context, OpenVpnService.class); if (mAuthenticationType == VpnProfile.TYPE_KEYSTORE || mAuthenticationType == VpnProfile.TYPE_USERPASS_KEYSTORE) { - if (getKeyStoreCertificates(context) == null) - return null; + // if (getKeyStoreCertificates(context) == null) + // return null; } intent.putExtra(prefix + ".ARGV", buildOpenvpnArgv(context.getCacheDir())); diff --git a/app/src/main/java/de/blinkt/openvpn/views/SeekBarTicks.java b/app/src/main/java/de/blinkt/openvpn/views/SeekBarTicks.java new file mode 100644 index 00000000..88e8e164 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/views/SeekBarTicks.java @@ -0,0 +1,69 @@ +package de.blinkt.openvpn.views; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.ViewConfiguration; +import android.widget.SeekBar; + +public class SeekBarTicks extends SeekBar { + private Paint mTickPaint; + private float mTickHeight; + + private float tickHeightRatio = 0.6f; + + public SeekBarTicks(Context context, AttributeSet attrs) { + super (context, attrs); + + initTicks (context, attrs, android.R.attr.seekBarStyle); + } + + + public SeekBarTicks(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + initTicks (context, attrs, defStyle); + + /*mTickHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + tickHeightDP, + ctx.getResources().getDisplayMetrics()); */ + } + + private void initTicks(Context context, AttributeSet attrs, int defStyle) { + TypedArray a = context.obtainStyledAttributes(attrs, + new int[] { android.R.attr.secondaryProgress }, defStyle, 0); + + + int tickColor = a.getColor(0, android.R.color.black); + mTickPaint = new Paint(); + mTickPaint.setColor( context.getResources().getColor(tickColor)); + a.recycle(); + } + + + @Override + protected synchronized void onDraw(Canvas canvas) { + drawTicks(canvas); + super.onDraw(canvas); + } + + private void drawTicks(Canvas canvas) { + + final int available = getWidth() - getPaddingLeft() - getPaddingRight(); + final int availableHeight = getHeight() - getPaddingBottom() - getPaddingTop(); + + int extrapadding = (int) ((availableHeight- (availableHeight * tickHeightRatio))/2); + + int tickSpacing = available / (getMax() ); + + for (int i = 1; i < getMax(); i++) { + final float x = getPaddingLeft() + i * tickSpacing; + + canvas.drawLine(x, getPaddingTop()+extrapadding, x, getHeight()-getPaddingBottom()-extrapadding, mTickPaint); + } + } +} -- cgit v1.2.3 From 3532eff1b86f6b5ae4268393c4b5c61350c89faf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parm=C3=A9nides=20GV?= Date: Thu, 12 Jun 2014 18:41:48 +0200 Subject: Notification heads to Dashboard. Previously, it drived the user to the LogWindow. --- app/src/main/java/de/blinkt/openvpn/core/OpenVpnService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/src/main/java/de/blinkt/openvpn') 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 d7bb827e..010cc4f0 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVpnService.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVpnService.java @@ -244,7 +244,7 @@ public class OpenVpnService extends VpnService implements StateListener, Callbac PendingIntent getLogPendingIntent() { // Let the configure Button show the Log - Intent intent = new Intent(getBaseContext(), LogWindow.class); + Intent intent = new Intent(getBaseContext(), se.leap.bitmaskclient.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); -- cgit v1.2.3