diff options
Diffstat (limited to 'app/src/main/java')
13 files changed, 598 insertions, 507 deletions
diff --git a/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java b/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java index 721e8991..16f986ae 100644 --- a/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java +++ b/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java @@ -20,7 +20,6 @@ import android.preference.PreferenceManager; import android.text.InputType; import android.text.TextUtils; import android.text.method.PasswordTransformationMethod; -import android.util.Log; import android.view.View; import android.widget.CheckBox; import android.widget.CompoundButton; @@ -36,202 +35,179 @@ import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus; /** * This Activity actually handles two stages of a launcher shortcut's life cycle. - * + * <p/> * 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. - * + * 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. + * <p/> * 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. - * + * Typically this would then be handled as necessary by an activity within + * your application. + * <p/> * 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. - * + * <p/> * 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. - * + * <p/> * We handle stage 2 (responding to a shortcut) in this sample by simply displaying the contents * of the incoming {@link android.content.Intent}. - * + * <p/> * 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"; - public static final String CLEARLOG = "clearlogconnect"; + 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"; + public static final String CLEARLOG = "clearlogconnect"; - private static final int START_VPN_PROFILE= 70; + private static final int START_VPN_PROFILE = 70; - private ProfileManager mPM; - private VpnProfile mSelectedProfile; - private boolean mhideLog=false; + private VpnProfile mSelectedProfile; + private boolean mhideLog = false; - private boolean mCmfixed=false; + private boolean mCmfixed = false; - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); - mPM =ProfileManager.getInstance(this); + startVpnFromIntent(); + } - } + protected void startVpnFromIntent() { + // Resolve the intent - @Override - protected void onStart() { - super.onStart(); - // Resolve the intent + final Intent intent = getIntent(); + final String action = intent.getAction(); - 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 the intent is a request to create a shortcut, we'll do that and exit + if (Intent.ACTION_MAIN.equals(action)) { + // Check if we need to clear the log + if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean(CLEARLOG, true)) + VpnStatus.clearLog(); - if(Intent.ACTION_MAIN.equals(action)) { - // Check if we need to clear the log - if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean(CLEARLOG, true)) - VpnStatus.clearLog(); + // 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); - // 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); - 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(); + } else { + mSelectedProfile = profileToConnect; + launchVPN(); + } + } + } - if(profileToConnect ==null) { - VpnStatus.logError(R.string.shortcut_profile_notfound); - // show Log window to display error - showLogWindow(); - finish(); - return; - } + @Override + protected void onActivityResult (int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); - mSelectedProfile = profileToConnect; - launchVPN(); + if(requestCode==START_VPN_PROFILE) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + boolean showLogWindow = prefs.getBoolean("showlogwindow", true); - } - } - - @Override - protected void onActivityResult (int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - if(requestCode==START_VPN_PROFILE) { - if(resultCode == Activity.RESULT_OK) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - boolean showLogWindow = prefs.getBoolean("showlogwindow", true); - - if(!mhideLog && showLogWindow) - showLogWindow(); - new startOpenVpnThread().start(); - } else if (resultCode == Activity.RESULT_CANCELED) { - // User does not want us to start, so we just vanish - VpnStatus.updateStateString("USER_VPN_PERMISSION_CANCELLED", "", R.string.state_user_vpn_permission_cancelled, - ConnectionStatus.LEVEL_NOTCONNECTED); - - finish(); - } - } - } + if(!mhideLog && showLogWindow) + showLogWindow(); - void showLogWindow() { - - Intent startLW = new Intent(getBaseContext(),LogWindow.class); - startLW.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); - startActivity(startLW); + VPNLaunchHelper.startOpenVpn(mSelectedProfile, getBaseContext()); + finish(); + } 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 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() { + void showLogWindow() { - @Override - public void onClick(DialogInterface dialog, int which) { - finish(); + Intent startLW = new Intent(getBaseContext(), LogWindow.class); + startLW.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); + startActivity(startLW); - } - }); - d.show(); - } + } - void launchVPN () { - int vpnok = mSelectedProfile.checkProfile(this); - if(vpnok!= R.string.no_error_found) { - showConfigErrorDialog(vpnok); - return; - } + 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() { - 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); + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); - if(loadTunModule) - execeuteSUcmd("insmod /system/lib/modules/tun.ko"); + } + }); + d.show(); + } - if(usecm9fix && !mCmfixed ) { - execeuteSUcmd("chown system /dev/tun"); - } + 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 (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); - } - - } + if (loadTunModule) + execeuteSUcmd("insmod /system/lib/modules/tun.ko"); - 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); + if (usecm9fix && !mCmfixed) { + execeuteSUcmd("chown system /dev/tun"); + } - } catch (IOException e) { + 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) { + try { + ProcessBuilder pb = new ProcessBuilder("su", "-c", command); + Process p = pb.start(); + int ret = p.waitFor(); + if (ret == 0) + mCmfixed = true; + } catch (InterruptedException | 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/VpnProfile.java b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java index dbe4b440..38d76f68 100644 --- a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java +++ b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java @@ -64,7 +64,7 @@ public class VpnProfile implements Serializable, Cloneable { // 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 + 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]]"; @@ -150,18 +150,23 @@ public class VpnProfile implements Serializable, Cloneable { private int mProfileVersion; public String mExcludedRoutes; public String mExcludedRoutesv6; - public int mMssFix =0; // -1 is default, + public int mMssFix = 0; // -1 is default, public Connection[] mConnections = new Connection[0]; - public boolean mRemoteRandom=false; + public boolean mRemoteRandom = false; public HashSet<String> mAllowedAppsVpn = new HashSet<>(); public boolean mAllowedAppsVpnAreDisallowed = true; + + public String mCrlFilename; public String mProfileCreator; - /* Options no long used in new profiles */ + + public boolean mPushPeerInfo = false; + public static final boolean mIsOpenVPN22 = false; + + /* Options no longer used in new profiles */ public String mServerName = "openvpn.blinkt.de"; public String mServerPort = "1194"; public boolean mUseUdp = true; - public boolean mPushPeerInfo=false; public VpnProfile(String name) { mUuid = UUID.randomUUID(); @@ -169,7 +174,7 @@ public class VpnProfile implements Serializable, Cloneable { mProfileVersion = CURRENT_PROFILE_VERSION; mConnections = new Connection[1]; - mConnections[0] = new Connection(); + mConnections[0] = new Connection(); } public static String openVpnEscape(String unescaped) { @@ -197,7 +202,7 @@ public class VpnProfile implements Serializable, Cloneable { mCheckRemoteCN = false; mPersistTun = false; mAllowLocalLAN = true; - mPushPeerInfo =false; + mPushPeerInfo = false; mMssFix = 0; } @@ -207,33 +212,33 @@ public class VpnProfile implements Serializable, Cloneable { } public String getName() { - if (mName==null) + if (mName == null) return "No profile name"; return mName; } - public void upgradeProfile(){ - if(mProfileVersion< 2) { + public void upgradeProfile() { + if (mProfileVersion < 2) { /* default to the behaviour the OS used */ mAllowLocalLAN = Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT; } if (mProfileVersion < 4) { moveOptionsToConnection(); - mAllowedAppsVpnAreDisallowed=true; + mAllowedAppsVpnAreDisallowed = true; } - if (mAllowedAppsVpn==null) + if (mAllowedAppsVpn == null) mAllowedAppsVpn = new HashSet<>(); - if (mConnections ==null) + if (mConnections == null) mConnections = new Connection[0]; if (mProfileVersion < 6) { if (TextUtils.isEmpty(mProfileCreator)) - mUserEditable=true; + mUserEditable = true; } - mProfileVersion= CURRENT_PROFILE_VERSION; + mProfileVersion = CURRENT_PROFILE_VERSION; } @@ -271,7 +276,7 @@ public class VpnProfile implements Serializable, Cloneable { cfg += String.format("setenv IV_GUI_VER %s \n", openVpnEscape(getVersionEnvString(context))); String versionString = String.format("%d %s %s %s %s %s", Build.VERSION.SDK_INT, Build.VERSION.RELEASE, NativeUtils.getNativeAPI(), Build.BRAND, Build.BOARD, Build.MODEL); - cfg += String.format("setenv IV_PLAT_VER %s\n", openVpnEscape(versionString)) ; + cfg += String.format("setenv IV_PLAT_VER %s\n", openVpnEscape(versionString)); } cfg += "machine-readable-output\n"; @@ -304,7 +309,8 @@ public class VpnProfile implements Serializable, Cloneable { mConnectRetry = "5"; - cfg += "connect-retry " + mConnectRetry + "\n"; + if (!mIsOpenVPN22 || !mUseUdp) + cfg += "connect-retry " + mConnectRetry + "\n"; cfg += "resolv-retry 60\n"; @@ -315,7 +321,7 @@ public class VpnProfile implements Serializable, Cloneable { boolean canUsePlainRemotes = true; - if (mConnections.length==1) { + if (mConnections.length == 1) { cfg += mConnections[0].getConnectionBlock(); } else { for (Connection conn : mConnections) { @@ -323,7 +329,7 @@ public class VpnProfile implements Serializable, Cloneable { } if (mRemoteRandom) - cfg+="remote-random\n"; + cfg += "remote-random\n"; if (canUsePlainRemotes) { for (Connection conn : mConnections) { @@ -378,6 +384,9 @@ public class VpnProfile implements Serializable, Cloneable { cfg += insertFileData("ca", mCaFilename); } + if (!TextUtils.isEmpty(mCrlFilename)) + cfg += insertFileData("crl-verify", mCrlFilename); + if (mUseLzo) { cfg += "comp-lzo\n"; } @@ -411,13 +420,12 @@ public class VpnProfile implements Serializable, Cloneable { if (mUseDefaultRoute) routes += "route 0.0.0.0 0.0.0.0 vpn_gateway\n"; - else - { + else { for (String route : getCustomRoutes(mCustomRoutes)) { routes += "route " + route + " vpn_gateway\n"; } - for (String route: getCustomRoutes(mExcludedRoutes)) { + for (String route : getCustomRoutes(mExcludedRoutes)) { routes += "route " + route + " net_gateway\n"; } } @@ -442,11 +450,11 @@ public class VpnProfile implements Serializable, Cloneable { } - if (mMssFix !=0){ - if (mMssFix!=1450) { + if (mMssFix != 0) { + if (mMssFix != 1450) { cfg += String.format("mssfix %d\n", mMssFix, Locale.US); } else - cfg+="mssfix\n"; + cfg += "mssfix\n"; } if (mNobind) @@ -507,11 +515,11 @@ public class VpnProfile implements Serializable, Cloneable { } if (mPushPeerInfo) - cfg+="push-peer-info\n"; + cfg += "push-peer-info\n"; SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); boolean usesystemproxy = prefs.getBoolean("usesystemproxy", true); - if (usesystemproxy) { + if (usesystemproxy && !mIsOpenVPN22) { cfg += "# Use system proxy setting\n"; cfg += "management-query-proxy\n"; } @@ -537,8 +545,6 @@ public class VpnProfile implements Serializable, Cloneable { } - - return cfg; } @@ -557,8 +563,7 @@ public class VpnProfile implements Serializable, Cloneable { //! Put inline data inline and other data as normal escaped filename public static String insertFileData(String cfgentry, String filedata) { if (filedata == null) { - // TODO: generate good error - return String.format("%s %s\n", cfgentry, "missing"); + return String.format("%s %s\n", cfgentry, "file missing in config profile"); } else if (isEmbedded(filedata)) { String dataWithOutHeader = getEmbeddedContent(filedata); return String.format(Locale.ENGLISH, "<%s>\n%s\n</%s>\n", cfgentry, dataWithOutHeader, cfgentry); @@ -629,39 +634,32 @@ public class VpnProfile implements Serializable, Cloneable { } - - public Intent prepareStartService(Context context) { Intent intent = getStartServiceIntent(context); + // TODO: Handle this?! +// if (mAuthenticationType == VpnProfile.TYPE_KEYSTORE || mAuthenticationType == VpnProfile.TYPE_USERPASS_KEYSTORE) { +// if (getKeyStoreCertificates(context) == null) +// return null; +// } - if (mAuthenticationType == VpnProfile.TYPE_KEYSTORE || mAuthenticationType == VpnProfile.TYPE_USERPASS_KEYSTORE) { - if (getKeyStoreCertificates(context) == null) - return null; - } - + return intent; + } - try { - FileWriter cfg = new FileWriter(VPNLaunchHelper.getConfigFilePath(context)); - cfg.write(getConfigFile(context, false)); - cfg.flush(); - cfg.close(); - } catch (IOException e) { - VpnStatus.logException(e); - } + public void writeConfigFile(Context context) throws IOException { + FileWriter cfg = new FileWriter(VPNLaunchHelper.getConfigFilePath(context)); + cfg.write(getConfigFile(context, false)); + cfg.flush(); + cfg.close(); - return intent; } public Intent getStartServiceIntent(Context context) { String prefix = context.getPackageName(); Intent intent = new Intent(context, OpenVPNService.class); - intent.putExtra(prefix + ".ARGV", VPNLaunchHelper.buildOpenvpnArgv(context)); intent.putExtra(prefix + ".profileUUID", mUuid.toString()); - ApplicationInfo info = context.getApplicationInfo(); - intent.putExtra(prefix + ".nativelib", info.nativeLibraryDir); return intent; } @@ -672,11 +670,10 @@ public class VpnProfile implements Serializable, Cloneable { public static String getDisplayName(String embeddedFile) { int start = DISPLAYNAME_TAG.length(); int end = embeddedFile.indexOf(INLINE_TAG); - return embeddedFile.substring(start,end); + return embeddedFile.substring(start, end); } - public static String getEmbeddedContent(String data) - { + public static String getEmbeddedContent(String data) { if (!data.contains(INLINE_TAG)) return data; @@ -685,7 +682,7 @@ public class VpnProfile implements Serializable, Cloneable { } public static boolean isEmbedded(String data) { - if (data==null) + if (data == null) return false; if (data.startsWith(INLINE_TAG) || data.startsWith(DISPLAYNAME_TAG)) return true; @@ -697,8 +694,8 @@ public class VpnProfile implements Serializable, Cloneable { /* This method is called when OpenVPNService is restarted */ if ((mAuthenticationType == VpnProfile.TYPE_KEYSTORE || mAuthenticationType == VpnProfile.TYPE_USERPASS_KEYSTORE) - && mPrivateKey==null) { - new Thread( new Runnable() { + && mPrivateKey == null) { + new Thread(new Runnable() { @Override public void run() { getKeyStoreCertificates(context); @@ -713,9 +710,9 @@ public class VpnProfile implements Serializable, Cloneable { VpnProfile copy = (VpnProfile) super.clone(); copy.mUuid = UUID.randomUUID(); copy.mConnections = new Connection[mConnections.length]; - int i=0; - for (Connection conn: mConnections) { - copy.mConnections[i++]=conn.clone(); + int i = 0; + for (Connection conn : mConnections) { + copy.mConnections[i++] = conn.clone(); } copy.mAllowedAppsVpn = (HashSet<String>) mAllowedAppsVpn.clone(); return copy; @@ -735,12 +732,12 @@ public class VpnProfile implements Serializable, Cloneable { class NoCertReturnedException extends Exception { - public NoCertReturnedException (String msg) { + public NoCertReturnedException(String msg) { super(msg); } } - synchronized String[] getKeyStoreCertificates(Context context,int tries) { + synchronized String[] getKeyStoreCertificates(Context context, int tries) { try { PrivateKey privateKey = KeyChain.getPrivateKey(context, mAlias); mPrivateKey = privateKey; @@ -749,7 +746,7 @@ public class VpnProfile implements Serializable, Cloneable { X509Certificate[] caChain = KeyChain.getCertificateChain(context, mAlias); - if(caChain == null) + if (caChain == null) throw new NoCertReturnedException("No certificate returned from Keystore"); if (caChain.length <= 1 && TextUtils.isEmpty(mCaFilename)) { @@ -774,10 +771,10 @@ public class VpnProfile implements Serializable, Cloneable { StringWriter caoutWriter = new StringWriter(); PemWriter pw = new PemWriter(caoutWriter); - for (Certificate cert: cacerts) + for (Certificate cert : cacerts) pw.writeObject(new PemObject("CERTIFICATE", cert.getEncoded())); pw.close(); - caout= caoutWriter.toString(); + caout = caoutWriter.toString(); } catch (Exception e) { VpnStatus.logError("Could not read CA certificate" + e.getLocalizedMessage()); @@ -800,12 +797,12 @@ public class VpnProfile implements Serializable, Cloneable { String ca, extra; - if(caout==null) { - ca =keystoreChain; - extra=null; + if (caout == null) { + ca = keystoreChain; + extra = null; } else { ca = caout; - extra=keystoreChain; + extra = keystoreChain; } return new String[]{ca, extra, user}; @@ -823,15 +820,15 @@ public class VpnProfile implements Serializable, Cloneable { return null; } catch (AssertionError e) { - if (tries ==0) + if (tries == 0) return null; - VpnStatus.logError(String.format("Failure getting Keystore Keys (%s), retrying",e.getLocalizedMessage())); + 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); + return getKeyStoreCertificates(context, tries - 1); } } @@ -848,20 +845,32 @@ public class VpnProfile implements Serializable, Cloneable { return R.string.ipv4_format_error; } if (!mUseDefaultRoute) { - if (!TextUtils.isEmpty(mCustomRoutes) && getCustomRoutes(mCustomRoutes).size() == 0 ) + if (!TextUtils.isEmpty(mCustomRoutes) && getCustomRoutes(mCustomRoutes).size() == 0) return R.string.custom_route_format_error; - if (!TextUtils.isEmpty(mExcludedRoutes) && getCustomRoutes(mExcludedRoutes).size() == 0 ) + if (!TextUtils.isEmpty(mExcludedRoutes) && getCustomRoutes(mExcludedRoutes).size() == 0) return R.string.custom_route_format_error; } + if (mUseTLSAuth && TextUtils.isEmpty(mTLSAuthFilename)) + return R.string.missing_tlsauth; + + if ((mAuthenticationType == TYPE_USERPASS_CERTIFICATES || mAuthenticationType == TYPE_CERTIFICATES) + && (TextUtils.isEmpty(mClientCertFilename) || TextUtils.isEmpty(mClientKeyFilename))) + return R.string.missing_certificates; + + if ((mAuthenticationType == TYPE_CERTIFICATES || mAuthenticationType == TYPE_USERPASS_CERTIFICATES) + && TextUtils.isEmpty(mCaFilename)) + return R.string.missing_ca_certificate; + + boolean noRemoteEnabled = true; for (Connection c : mConnections) if (c.mEnabled) noRemoteEnabled = false; - if(noRemoteEnabled) + if (noRemoteEnabled) return R.string.remote_no_server_selected; // Everything okay @@ -956,7 +965,7 @@ public class VpnProfile implements Serializable, Cloneable { if (isUserPWAuth() && (TextUtils.isEmpty(mUsername) || - (TextUtils.isEmpty(mPassword) && (mTransientPW == null || ignoreTransient)))) { + (TextUtils.isEmpty(mPassword) && (mTransientPW == null || ignoreTransient)))) { return R.string.password; } return 0; 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 f55de486..d25bccad 100644 --- a/app/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java +++ b/app/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java @@ -81,7 +81,7 @@ public class DisconnectVPN extends Activity implements DialogInterface.OnClickLi if (which == DialogInterface.BUTTON_POSITIVE) { ProfileManager.setConntectedVpnProfileDisconnected(this); if (mService != null && mService.getManagement() != null) - mService.getManagement().stopVPN(); + mService.getManagement().stopVPN(false); } finish(); } 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 80a15c54..d14e643e 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java +++ b/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java @@ -32,8 +32,6 @@ public class ConfigParser { private HashMap<String, Vector<Vector<String>>> options = new HashMap<String, Vector<Vector<String>>>(); private HashMap<String, Vector<String>> meta = new HashMap<String, Vector<String>>(); private String auth_user_pass_file; - private String crl_verify_file; - public void parseConfig(Reader reader) throws IOException, ConfigParseError { @@ -132,10 +130,6 @@ public class ConfigParser { return auth_user_pass_file; } - public String getCrlVerifyFile() { - return crl_verify_file; - } - enum linestate { initial, readin_single_quote, reading_quoted, reading_unquoted, done @@ -299,7 +293,8 @@ public class ConfigParser { { {"setenv", "IV_GUI_VER"}, {"setenv", "IV_OPENVPN_GUI_VERSION"}, - {"engine", "dynamic"} + {"engine", "dynamic"}, + {"setenv", "CLIENT_CERT"} }; final String[] connectionOptions = { @@ -621,11 +616,12 @@ public class ConfigParser { Vector<String> crlfile = getOption("crl-verify", 1, 2); if (crlfile != null) { // If the 'dir' parameter is present just add it as custom option .. - np.mCustomConfigOptions += TextUtils.join(" ", crlfile) + "\n"; - if (crlfile.size() == 2) { + if (crlfile.size() == 3 && crlfile.get(2).equals("dir")) + np.mCustomConfigOptions += TextUtils.join(" ", crlfile) + "\n"; + else // Save the filename for the config converter to add later - crl_verify_file = crlfile.get(1); - } + np.mCrlFilename = crlfile.get(1); + } @@ -813,16 +809,6 @@ public class ConfigParser { } } - public static void removeCRLCustomOption(VpnProfile np) { - String lines[] = np.mCustomConfigOptions.split("\\r?\\n"); - Vector<String> keeplines = new Vector<>(); - for (String l : lines) { - if (!l.startsWith("crl-verify ")) - keeplines.add(l); - } - np.mCustomConfigOptions = TextUtils.join("\n", keeplines); - } - private void checkIgnoreAndInvalidOptions(VpnProfile np) throws ConfigParseError { for (String option : unsupportedOptions) if (options.containsKey(option)) diff --git a/app/src/main/java/de/blinkt/openvpn/core/LogFileHandler.java b/app/src/main/java/de/blinkt/openvpn/core/LogFileHandler.java index 5c1741d9..288c7934 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/LogFileHandler.java +++ b/app/src/main/java/de/blinkt/openvpn/core/LogFileHandler.java @@ -11,12 +11,15 @@ import android.os.Message; import android.os.Parcel; import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Locale; + +import se.leap.bitmaskclient.R; /** * Created by arne on 23.01.16. @@ -27,7 +30,6 @@ class LogFileHandler extends Handler { static final int LOG_INIT = 102; public static final int LOG_MESSAGE = 103; private static FileOutputStream mLogFile; - private static BufferedOutputStream mBufLogfile; public static final String LOGFILE_NAME = "logcache.dat"; @@ -41,6 +43,8 @@ class LogFileHandler extends Handler { public void handleMessage(Message msg) { try { if (msg.what == LOG_INIT) { + if (mLogFile != null) + throw new RuntimeException("mLogFile not null"); readLogCache((File) msg.obj); openLogFile((File) msg.obj); } else if (msg.what == LOG_MESSAGE && msg.obj instanceof VpnStatus.LogItem) { @@ -70,15 +74,13 @@ class LogFileHandler extends Handler { private static void trimLogFile() { try { - mBufLogfile.flush(); + mLogFile.flush(); mLogFile.getChannel().truncate(0); - } catch (IOException e) { e.printStackTrace(); } } - private void writeLogItemToDisk(VpnStatus.LogItem li) throws IOException { Parcel p = Parcel.obtain(); li.writeToParcel(p, 0); @@ -86,8 +88,8 @@ class LogFileHandler extends Handler { // write binary format to disc byte[] liBytes = p.marshall(); - mLogFile.write(liBytes.length & 0xff); - mLogFile.write(liBytes.length >> 8); + byte[] lenBytes = ByteBuffer.allocate(4).putInt(liBytes.length).array(); + mLogFile.write(lenBytes); mLogFile.write(liBytes); p.recycle(); } @@ -95,39 +97,56 @@ class LogFileHandler extends Handler { private void openLogFile (File cacheDir) throws FileNotFoundException { File logfile = new File(cacheDir, LOGFILE_NAME); mLogFile = new FileOutputStream(logfile); - mBufLogfile = new BufferedOutputStream(mLogFile); } private void readLogCache(File cacheDir) { File logfile = new File(cacheDir, LOGFILE_NAME); + if (!logfile.exists() || !logfile.canRead()) return; - VpnStatus.logDebug("Reread log items from cache file"); + try { + BufferedInputStream logFile = new BufferedInputStream(new FileInputStream(logfile)); byte[] buf = new byte[8192]; - int read = logFile.read(buf, 0, 2); + int read = logFile.read(buf, 0, 4); + int itemsRead=0; - while (read > 0) { - // Marshalled LogItem - int len = (0xff & buf[0]) | buf[1] << 8; + while (read >= 4) { + int len = ByteBuffer.wrap(buf, 0, 4).asIntBuffer().get(); + // Marshalled LogItem read = logFile.read(buf, 0, len); Parcel p = Parcel.obtain(); p.unmarshall(buf, 0, read); p.setDataPosition(0); VpnStatus.LogItem li = VpnStatus.LogItem.CREATOR.createFromParcel(p); - VpnStatus.newLogItem(li, true); + if (li.verify()) { + VpnStatus.newLogItem(li, true); + } else { + VpnStatus.logError(String.format(Locale.getDefault(), + "Could not read log item from file: %d/%d: %s", + read, len, bytesToHex(buf, Math.max(read,80)))); + } p.recycle(); //Next item - read = logFile.read(buf, 0, 2); + read = logFile.read(buf, 0, 4); + itemsRead++; + if (itemsRead > 2*VpnStatus.MAXLOGENTRIES) { + VpnStatus.logError("Too many logentries read from cache, aborting."); + read = 0; + } + } + VpnStatus.logDebug(R.string.reread_log, itemsRead); + + } catch (java.io.IOException | java.lang.RuntimeException e) { VpnStatus.logError("Reading cached logfile failed"); @@ -137,4 +156,17 @@ class LogFileHandler extends Handler { } } + final protected static char[] hexArray = "0123456789ABCDEF".toCharArray(); + public static String bytesToHex(byte[] bytes, int len) { + len = Math.min(bytes.length, len); + char[] hexChars = new char[len * 2]; + for ( int j = 0; j < len; j++ ) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = hexArray[v >>> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + return new String(hexChars); + } + + } diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java index 2771fa6a..2911fb1e 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java @@ -16,15 +16,19 @@ public interface OpenVPNManagement { screenOff } - int mBytecountInterval =2; + int mBytecountInterval = 2; - void reconnect(); + void reconnect(); - void pause(pauseReason reason); + void pause(pauseReason reason); - void resume(); + void resume(); - boolean stopVPN(); + /** + * @param replaceConnection True if the VPN is connected by a new connection. + * @return true if there was a process that has been send a stop signal + */ + boolean stopVPN(boolean replaceConnection); /* * Rebind the interface 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 17be29b0..2917bce1 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java @@ -32,6 +32,7 @@ import android.text.TextUtils; import android.util.Log; import android.widget.Toast; +import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.Inet6Address; @@ -87,6 +88,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac private final Object mProcessLock = new Object(); private Handler guiHandler; private Toast mlastToast; + private Runnable mOpenVPNThread; // 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) { @@ -116,7 +118,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac @Override public void onRevoke() { VpnStatus.logInfo(R.string.permission_revoked); - mManagement.stopVPN(); + mManagement.stopVPN(false); endVpnService(); } @@ -132,6 +134,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac VpnStatus.removeByteCountListener(this); unregisterDeviceStateReceiver(); ProfileManager.setConntectedVpnProfileDisconnected(this); + mOpenVPNThread = null; if (!mStarting) { stopForeground(!mNotificationAlwaysVisible); @@ -182,16 +185,16 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac mNotificationManager.notify(OPENVPN_STATUS, notification); - //startForeground(OPENVPN_STATUS, notification); + // startForeground(OPENVPN_STATUS, notification); // Check if running on a TV - if(runningOnAndroidTV() && !lowpriority) + if (runningOnAndroidTV() && !lowpriority) guiHandler.post(new Runnable() { @Override public void run() { - if (mlastToast!=null) + if (mlastToast != null) mlastToast.cancel(); String toastText = String.format(Locale.getDefault(), "%s - %s", mProfile.mName, msg); mlastToast = Toast.makeText(getBaseContext(), toastText, Toast.LENGTH_SHORT); @@ -378,38 +381,50 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac mProfile = ProfileManager.get(this, profileUUID); } + /* start the OpenVPN process itself in a background thread */ + new Thread(new Runnable() { + @Override + public void run() { + startOpenVPN(); + } + }).start(); + + + ProfileManager.setConnectedVpnProfile(this, mProfile); + /* TODO: At the moment we have no way to handle asynchronous PW input + * Fixing will also allow to handle challenge/response authentication */ + if (mProfile.needUserPWInput(true) != 0) + return START_NOT_STICKY; + + return START_STICKY; + } + + private void startOpenVPN() { + VpnStatus.logInfo(R.string.building_configration); + VpnStatus.updateStateString("VPN_GENERATE_CONFIG", "", R.string.building_configration, VpnStatus.ConnectionStatus.LEVEL_START); + + + try { + mProfile.writeConfigFile(this); + } catch (IOException e) { + VpnStatus.logException("Error writing config file", e); + endVpnService(); + return; + } // Extract information from the intent. String prefix = getPackageName(); - String[] argv = intent.getStringArrayExtra(prefix + ".ARGV"); - String nativeLibraryDirectory = intent.getStringExtra(prefix + ".nativelib"); + String nativeLibraryDirectory = getApplicationInfo().nativeLibraryDir; + + // Also writes OpenVPN binary + String[] argv = VPNLaunchHelper.buildOpenvpnArgv(this); - 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 - } - synchronized (mProcessLock) { - if (mProcessThread != null) { - mProcessThread.interrupt(); - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - //ignore - } - } - } + stopOldOpenVPNProcess(); // An old running VPN should now be exited mStarting = false; @@ -420,10 +435,8 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac 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)) { @@ -433,13 +446,15 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac mManagement = ovpnManagementThread; VpnStatus.logInfo("started Socket Thread"); } else { - return START_NOT_STICKY; + endVpnService(); + return; } } - Runnable processThread; - if (mOvpn3) { + if (mOvpn3) + + { OpenVPNManagement mOpenVPN3 = instantiateOpenVPN3Core(); processThread = (Runnable) mOpenVPN3; @@ -449,25 +464,53 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } else { HashMap<String, String> env = new HashMap<>(); processThread = new OpenVPNThread(this, argv, env, nativeLibraryDirectory); + mOpenVPNThread = processThread; } - synchronized (mProcessLock) { + synchronized (mProcessLock) + + { mProcessThread = new Thread(processThread, "OpenVPNProcessThread"); mProcessThread.start(); } - if (mDeviceStateReceiver != null) - unregisterDeviceStateReceiver(); - registerDeviceStateReceiver(mManagement); + new Handler(getMainLooper()).post(new Runnable() { + @Override + public void run() { + if (mDeviceStateReceiver != null) + unregisterDeviceStateReceiver(); + registerDeviceStateReceiver(mManagement); + } + } - ProfileManager.setConnectedVpnProfile(this, mProfile); - /* TODO: At the moment we have no way to handle asynchronous PW input - * Fixing will also allow to handle challenge/response authentication */ - if (mProfile.needUserPWInput(true) != 0) - return START_NOT_STICKY; + ); + } - return START_STICKY; + private void stopOldOpenVPNProcess() { + if (mManagement != null) { + if (mOpenVPNThread!=null) + ((OpenVPNThread) mOpenVPNThread).setReplaceConnection(); + if (mManagement.stopVPN(true)) { + // an old was asked to exit, wait 1s + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + //ignore + } + } + } + + synchronized (mProcessLock) { + if (mProcessThread != null) { + mProcessThread.interrupt(); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + //ignore + } + } + } } private OpenVPNManagement instantiateOpenVPN3Core() { @@ -475,7 +518,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac Class cl = Class.forName("de.blinkt.openvpn.core.OpenVPNThreadv3"); return (OpenVPNManagement) cl.getConstructor(OpenVPNService.class, VpnProfile.class).newInstance(this, mProfile); } catch (IllegalArgumentException | InstantiationException | InvocationTargetException | - NoSuchMethodException | ClassNotFoundException | IllegalAccessException e ) { + NoSuchMethodException | ClassNotFoundException | IllegalAccessException e) { e.printStackTrace(); } return null; @@ -485,7 +528,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac public void onDestroy() { synchronized (mProcessLock) { if (mProcessThread != null) { - mManagement.stopVPN(); + mManagement.stopVPN(true); } } @@ -581,7 +624,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac // Check if the first DNS Server is in the VPN range try { ipAddress dnsServer = new ipAddress(new CIDRIP(mDnslist.get(0), 32), true); - boolean dnsIncluded=false; + boolean dnsIncluded = false; for (ipAddress net : positiveIPv4Routes) { if (net.containsNet(dnsServer)) { dnsIncluded = true; @@ -620,8 +663,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } - - if (mDomain != null) builder.addSearchDomain(mDomain); @@ -716,7 +757,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac @TargetApi(Build.VERSION_CODES.LOLLIPOP) private void setAllowedVpnPackages(Builder builder) { - boolean atLeastOneAllowedApp=false; + boolean atLeastOneAllowedApp = false; for (String pkg : mProfile.mAllowedAppsVpn) { try { if (mProfile.mAllowedAppsVpnAreDisallowed) { @@ -910,7 +951,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac // Does not work :( String msg = getString(resid); // showNotification(VpnStatus.getLastCleanLogMessage(this), - // msg, lowpriority, 0, level); + // msg, lowpriority, 0, level); } } 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 10bc9e87..e0c39546 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java @@ -34,51 +34,56 @@ public class OpenVPNThread implements Runnable { @SuppressLint("SdCardPath") private static final String BROKEN_PIE_SUPPORT = "/data/data/de.blinkt.openvpn/cache/pievpn"; private final static String BROKEN_PIE_SUPPORT2 = "syntax error"; - private static final String TAG = "OpenVPN"; + 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<String, String> mProcessEnv; - private boolean mBrokenPie=false; - - public OpenVPNThread(OpenVPNService service,String[] argv, Map<String,String> 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) { + private Process mProcess; + private String mNativeDir; + private OpenVPNService mService; + private String mDumpPath; + private Map<String, String> mProcessEnv; + private boolean mBrokenPie = false; + private boolean mNoProcessExitStatus = false; + + public OpenVPNThread(OpenVPNService service, String[] argv, Map<String, String> processEnv, String nativelibdir) { + mArgv = argv; + mNativeDir = nativelibdir; + mService = service; + mProcessEnv = processEnv; + } + + public void stopProcess() { + mProcess.destroy(); + } + + void setReplaceConnection() + { + mNoProcessExitStatus=true; + } + + @Override + public void run() { + try { + Log.i(TAG, "Starting openvpn"); + startOpenVPNThreadArgs(mArgv, mProcessEnv); + Log.i(TAG, "OpenVPN process exited"); + } 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); if (mBrokenPie) { /* This will probably fail since the NoPIE binary is probably not written */ @@ -95,70 +100,72 @@ public class OpenVPNThread implements Runnable { } } - - 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<String, String> env) { - LinkedList<String> argvlist = new LinkedList<String>(); + + if (!mNoProcessExitStatus) + 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<String, String> env) { + LinkedList<String> argvlist = new LinkedList<String>(); 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<String,String> 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()); + + 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<String, String> 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()); if (logline.startsWith(BROKEN_PIE_SUPPORT) || logline.contains(BROKEN_PIE_SUPPORT2)) mBrokenPie = true; - + // 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); + if (m.matches()) { + int flags = Integer.parseInt(m.group(3), 16); String msg = m.group(4); int logLevel = flags & 0x0F; @@ -166,45 +173,45 @@ public class OpenVPNThread implements Runnable { if ((flags & M_FATAL) != 0) logStatus = VpnStatus.LogLevel.ERROR; - else if ((flags & M_NONFATAL)!=0) + else if ((flags & M_NONFATAL) != 0) logStatus = VpnStatus.LogLevel.WARNING; - else if ((flags & M_WARN)!=0) + else if ((flags & M_WARN) != 0) logStatus = VpnStatus.LogLevel.WARNING; - else if ((flags & M_DEBUG)!=0) + 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); + 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].replaceFirst("/cache/.*$" , "/lib"); - - String lbpath = pb.environment().get("LD_LIBRARY_PATH"); - if(lbpath==null) - lbpath = applibpath; - else - lbpath = applibpath + ":" + lbpath; - - if (!applibpath.equals(mNativeDir)) { - lbpath = mNativeDir + ":" + lbpath; - } - return lbpath; - } + } + + + } 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].replaceFirst("/cache/.*$", "/lib"); + + String lbpath = pb.environment().get("LD_LIBRARY_PATH"); + if (lbpath == null) + lbpath = applibpath; + else + lbpath = applibpath + ":" + lbpath; + + if (!applibpath.equals(mNativeDir)) { + lbpath = mNativeDir + ":" + lbpath; + } + 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 4c550f47..569a3846 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java @@ -49,6 +49,7 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { private pauseReason lastPauseReason = pauseReason.noNetwork; private PausedStateCallback mPauseCallback; + private boolean mShuttingDown; public OpenVpnManagementThread(VpnProfile profile, OpenVPNService openVpnService) { mProfile = profile; @@ -223,7 +224,8 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { processByteCount(argument); break; case "STATE": - processState(argument); + if (!mShuttingDown) + processState(argument); break; case "PROXY": processProxyCMD(argument); @@ -452,7 +454,7 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { break; default: - Log.e(TAG, "Unkown needok command " + argument); + Log.e(TAG, "Unknown needok command " + argument); return; } @@ -621,7 +623,9 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { } @Override - public boolean stopVPN() { + public boolean stopVPN(boolean replaceConnection) { + mShuttingDown = true; return stopOpenVPN(); } + } 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 f2cf8cec..78f462e7 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java +++ b/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java @@ -27,7 +27,7 @@ public class VPNLaunchHelper { - static private String writeMiniVPN(Context context) { + private static String writeMiniVPN(Context context) { String[] abis; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) abis = getSupportedABIsLollipop(); @@ -73,12 +73,18 @@ public class VPNLaunchHelper { } - public static String[] buildOpenvpnArgv(Context c) { + static String[] buildOpenvpnArgv(Context c) { Vector<String> args = new Vector<>(); + String binaryName = writeMiniVPN(c); // Add fixed paramenters //args.add("/data/data/de.blinkt.openvpn/lib/openvpn"); - args.add(writeMiniVPN(c)); + if(binaryName==null) { + VpnStatus.logError("Error writing minivpn binary"); + return null; + } + + args.add(binaryName); args.add("--config"); args.add(getConfigFilePath(c)); @@ -126,14 +132,6 @@ public class VPNLaunchHelper { public static void startOpenVpn(VpnProfile startprofile, Context context) { - VpnStatus.logInfo(R.string.building_configration); - VpnStatus.updateStateString("VPN_GENERATE_CONFIG", "", R.string.building_configration, VpnStatus.ConnectionStatus.LEVEL_START); - if(writeMiniVPN(context)==null) { - VpnStatus.logError("Error writing minivpn binary"); - return; - } - - Intent startVPN = startprofile.prepareStartService(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 index 3ac1595c..1e2ccba3 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java +++ b/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java @@ -17,9 +17,12 @@ import android.os.Message; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import android.util.Log; import java.io.ByteArrayInputStream; +import java.io.DataOutputStream; import java.io.File; +import java.io.OutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.security.MessageDigest; @@ -34,6 +37,7 @@ import java.util.Locale; import java.util.UnknownFormatConversionException; import java.util.Vector; +import se.leap.bitmaskclient.BuildConfig; import se.leap.bitmaskclient.R; public class VpnStatus { @@ -73,7 +77,7 @@ public class VpnStatus { logException(LogLevel.ERROR, context, e); } - private static final int MAXLOGENTRIES = 1000; + static final int MAXLOGENTRIES = 1000; public static String getLastCleanLogMessage(Context c) { @@ -291,7 +295,6 @@ public class VpnStatus { if (mArgs != null) str += TextUtils.join("|", mArgs); - return str; } } @@ -369,6 +372,16 @@ public class VpnStatus { } return mVerbosityLevel; } + + public boolean verify() { + if (mLevel == null) + return false; + + if (mMessage == null && mRessourceId == 0) + return false; + + return true; + } } public interface LogListener { @@ -575,6 +588,9 @@ public class VpnStatus { mLogFileHandler.sendMessage(mLogFileHandler.obtainMessage(LogFileHandler.TRIM_LOG_FILE)); } + if (BuildConfig.DEBUG && !cachedLine) + Log.d("OpenVPN", logItem.getString(null)); + for (LogListener ll : logListener) { ll.newLog(logItem); @@ -613,8 +629,8 @@ public class VpnStatus { 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; + long diffIn = mlastByteCount[2] = Math.max(0, in - lastIn); + long diffOut = mlastByteCount[3] = Math.max(0, out - lastOut); mlastByteCount = new long[]{in, out, diffIn, diffOut}; 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 f75e459e..bbd52a34 100644 --- a/app/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java +++ b/app/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java @@ -429,34 +429,33 @@ 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) { - ladapter.shareLog(); - } else if (item.getItemId() == R.id.edit_vpn) { - VpnProfile lastConnectedprofile = ProfileManager.getLastConnectedVpn(); - - if (lastConnectedprofile != null) { - Intent vprefintent = new Intent(getActivity(), Dashboard.class) - .putExtra(VpnProfile.EXTRA_PROFILEUUID, lastConnectedprofile.getUUIDString()); - startActivityForResult(vprefintent, START_VPN_CONFIG); - } else { - Toast.makeText(getActivity(), R.string.log_no_last_vpn, Toast.LENGTH_LONG).show(); - } - } else if (item.getItemId() == R.id.toggle_time) { - showHideOptionsPanel(); - } else if (item.getItemId() == android.R.id.home) { - // This is called when the Home (Up) button is pressed - // in the Action Bar. - Intent parentActivityIntent = new Intent(getActivity(), Dashboard.class); - parentActivityIntent.addFlags( - Intent.FLAG_ACTIVITY_CLEAR_TOP | - Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(parentActivityIntent); - getActivity().finish(); - return true; - - } - return super.onOptionsItemSelected(item); - + } else if(item.getItemId()==R.id.send) { + ladapter.shareLog(); + } else if(item.getItemId()==R.id.edit_vpn) { + VpnProfile lastConnectedprofile = ProfileManager.getLastConnectedVpn(); + + if(lastConnectedprofile!=null) { + Intent vprefintent = new Intent(getActivity(),Dashboard.class) + .putExtra(VpnProfile.EXTRA_PROFILEUUID,lastConnectedprofile.getUUIDString()); + startActivityForResult(vprefintent,START_VPN_CONFIG); + } else { + Toast.makeText(getActivity(), R.string.log_no_last_vpn, Toast.LENGTH_LONG).show(); + } + } else if(item.getItemId() == R.id.toggle_time) { + showHideOptionsPanel(); + } else if(item.getItemId() == android.R.id.home) { + // This is called when the Home (Up) button is pressed + // in the Action Bar. + Intent parentActivityIntent = new Intent(getActivity(), Dashboard.class); + parentActivityIntent.addFlags( + Intent.FLAG_ACTIVITY_CLEAR_TOP | + Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(parentActivityIntent); + getActivity().finish(); + return true; + + } + return super.onOptionsItemSelected(item); } private void showHideOptionsPanel() { diff --git a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java index 218b22a7..dbdf0a51 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java +++ b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java @@ -88,36 +88,55 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - app = this; - - PRNGFixes.apply(); - VpnStatus.initLogCache(getApplicationContext().getCacheDir()); - preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); fragment_manager = new FragmentManagerEnhanced(getFragmentManager()); - handleVersion(); - User.init(getString(R.string.default_username)); ProviderAPICommand.initialize(this); providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler(), this); - restoreProvider(savedInstanceState); - if (!provider.isConfigured()) + if (app == null) { + app = this; + + PRNGFixes.apply(); + VpnStatus.initLogCache(getApplicationContext().getCacheDir()); + handleVersion(); + User.init(getString(R.string.default_username)); + } + boolean provider_exists = previousProviderExists(savedInstanceState); + if (provider_exists) { + provider = getProvider(savedInstanceState); + if(!provider.isConfigured()) + startActivityForResult(new Intent(this, ConfigurationWizard.class), CONFIGURE_LEAP); + else { + buildDashboard(getIntent().getBooleanExtra(ON_BOOT, false)); + user_status_fragment.restoreSessionStatus(savedInstanceState); + } + } else { startActivityForResult(new Intent(this, ConfigurationWizard.class), CONFIGURE_LEAP); - else { - buildDashboard(getIntent().getBooleanExtra(ON_BOOT, false)); - user_status_fragment.restoreSessionStatus(savedInstanceState); } } - private void restoreProvider(Bundle savedInstanceState) { - if (savedInstanceState != null) { - if (savedInstanceState.containsKey(Provider.KEY)) - provider = savedInstanceState.getParcelable(Provider.KEY); - } - if (!provider.isConfigured() && preferences.getBoolean(Constants.PROVIDER_CONFIGURED, false)) + private boolean previousProviderExists(Bundle savedInstanceState) { + return providerInSavedInstance(savedInstanceState) || providerInSharedPreferences(); + } + + private Provider getProvider(Bundle savedInstanceState) { + if(providerInSavedInstance(savedInstanceState)) + provider = savedInstanceState.getParcelable(Provider.KEY); + else if (providerInSharedPreferences()) provider = getSavedProviderFromSharedPreferences(); + return provider; + } + + private boolean providerInSavedInstance(Bundle savedInstanceState) { + return savedInstanceState != null && + savedInstanceState.containsKey(Provider.KEY); + } + + private boolean providerInSharedPreferences() { + return preferences != null && + preferences.getBoolean(Constants.PROVIDER_CONFIGURED, false); + } @Override |