diff options
Diffstat (limited to 'app/src/main/java')
29 files changed, 2010 insertions, 1047 deletions
diff --git a/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java b/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java index 90216a70..16f986ae 100644 --- a/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java +++ b/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2014 Arne Schwabe + * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ @@ -35,196 +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 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 boolean mCmfixed=false; + private VpnProfile mSelectedProfile; + private boolean mhideLog = false; - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); + private boolean mCmfixed = false; - mPM =ProfileManager.getInstance(this); + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); - } + startVpnFromIntent(); + } - @Override - protected void onStart() { - super.onStart(); - // Resolve the intent + protected void startVpnFromIntent() { + // 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)) { - // 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); + if (Intent.ACTION_MAIN.equals(action)) { + // Check if we need to clear the log + if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean(CLEARLOG, true)) + VpnStatus.clearLog(); - VpnProfile profileToConnect = ProfileManager.get(this,shortcutUUID); - if(shortcutName != null && profileToConnect ==null) - profileToConnect = ProfileManager.getInstance(this).getProfileByName(shortcutName); + // 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); - if(profileToConnect ==null) { - VpnStatus.logError(R.string.shortcut_profile_notfound); - // show Log window to display error - showLogWindow(); - finish(); - return; - } + VpnProfile profileToConnect = ProfileManager.get(this, shortcutUUID); + if (shortcutName != null && profileToConnect == null) + profileToConnect = ProfileManager.getInstance(this).getProfileByName(shortcutName); - mSelectedProfile = profileToConnect; - launchVPN(); + if (profileToConnect == null) { + VpnStatus.logError(R.string.shortcut_profile_notfound); + // show Log window to display error + showLogWindow(); + finish(); + } else { + mSelectedProfile = profileToConnect; + launchVPN(); + } + } + } - } - } - - @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(); - } - } - } - - void showLogWindow() { + @Override + protected void onActivityResult (int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); - Intent startLW = new Intent(getBaseContext(),LogWindow.class); - startLW.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); - startActivity(startLW); - - } + if(requestCode==START_VPN_PROFILE) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + boolean showLogWindow = prefs.getBoolean("showlogwindow", true); - 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() { + if(!mhideLog && showLogWindow) + showLogWindow(); - @Override - public void onClick(DialogInterface dialog, int which) { - finish(); + 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); - } - }); - d.show(); + finish(); } + } - void launchVPN () { - int vpnok = mSelectedProfile.checkProfile(this); - if(vpnok!= R.string.no_error_found) { - showConfigErrorDialog(vpnok); - return; - } + void showLogWindow() { - 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); + Intent startLW = new Intent(getBaseContext(), LogWindow.class); + startLW.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); + startActivity(startLW); - if(loadTunModule) - execeuteSUcmd("insmod /system/lib/modules/tun.ko"); + } - if(usecm9fix && !mCmfixed ) { - execeuteSUcmd("chown system /dev/tun"); - } + 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(); - 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); - } + } + }); + d.show(); + } - } + void launchVPN() { + int vpnok = mSelectedProfile.checkProfile(this); + if (vpnok != R.string.no_error_found) { + showConfigErrorDialog(vpnok); + return; + } - 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); + 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); - } catch (IOException e) { - VpnStatus.logException("SU command", e); - } - } - - private class startOpenVpnThread extends Thread { - - @Override - public void run() { - VPNLaunchHelper.startOpenVpn(mSelectedProfile, getBaseContext()); - finish(); - - } - - } + 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) { + 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); + } + } } diff --git a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java index 43e1b57c..38d76f68 100644 --- a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java +++ b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2014 Arne Schwabe + * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ @@ -18,6 +18,7 @@ import android.os.Build; import android.preference.PreferenceManager; import android.security.KeyChain; import android.security.KeyChainException; +import android.support.annotation.NonNull; import android.text.TextUtils; import android.util.Base64; @@ -63,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]]"; @@ -71,7 +72,7 @@ public class VpnProfile implements Serializable, Cloneable { private static final long serialVersionUID = 7085688938959334563L; public static final int MAXLOGLEVEL = 4; - public static final int CURRENT_PROFILE_VERSION = 5; + public static final int CURRENT_PROFILE_VERSION = 6; public static final int DEFAULT_MSSFIX_SIZE = 1450; public static String DEFAULT_DNS1 = "8.8.8.8"; public static String DEFAULT_DNS2 = "8.8.4.4"; @@ -149,27 +150,31 @@ 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 HashSet<String> mAllowedAppsVpn = new HashSet<String>(); + 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 VpnProfile(String name) { mUuid = UUID.randomUUID(); mName = name; mProfileVersion = CURRENT_PROFILE_VERSION; mConnections = new Connection[1]; - mConnections[0] = new Connection(); + mConnections[0] = new Connection(); } public static String openVpnEscape(String unescaped) { @@ -197,6 +202,7 @@ public class VpnProfile implements Serializable, Cloneable { mCheckRemoteCN = false; mPersistTun = false; mAllowLocalLAN = true; + mPushPeerInfo = false; mMssFix = 0; } @@ -206,27 +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) - mAllowedAppsVpn = new HashSet<String>(); - if (mConnections ==null) + if (mAllowedAppsVpn == null) + mAllowedAppsVpn = new HashSet<>(); + if (mConnections == null) mConnections = new Connection[0]; - mProfileVersion= CURRENT_PROFILE_VERSION; + if (mProfileVersion < 6) { + if (TextUtils.isEmpty(mProfileCreator)) + mUserEditable = true; + } + + + mProfileVersion = CURRENT_PROFILE_VERSION; } @@ -260,8 +272,12 @@ public class VpnProfile implements Serializable, Cloneable { cfg += "management-query-passwords\n"; cfg += "management-hold\n\n"; - if (!configForOvpn3) + if (!configForOvpn3) { 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 += "machine-readable-output\n"; @@ -293,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"; @@ -304,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) { @@ -312,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) { @@ -367,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"; } @@ -400,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"; } } @@ -431,11 +450,11 @@ public class VpnProfile implements Serializable, Cloneable { } - if (mMssFix !=0){ - if (mMssFix!=1450) - cfg+=String.format("mssfix %d\n", mMssFix, Locale.US); - else - cfg+="mssfix\n"; + if (mMssFix != 0) { + if (mMssFix != 1450) { + cfg += String.format("mssfix %d\n", mMssFix, Locale.US); + } else + cfg += "mssfix\n"; } if (mNobind) @@ -495,9 +514,12 @@ public class VpnProfile implements Serializable, Cloneable { cfg += "preresolve\n"; } + if (mPushPeerInfo) + 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"; } @@ -523,8 +545,6 @@ public class VpnProfile implements Serializable, Cloneable { } - - return cfg; } @@ -541,10 +561,9 @@ public class VpnProfile implements Serializable, Cloneable { } //! Put inline data inline and other data as normal escaped filename - private String insertFileData(String cfgentry, String filedata) { + 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); @@ -553,8 +572,9 @@ public class VpnProfile implements Serializable, Cloneable { } } + @NonNull private Collection<String> getCustomRoutes(String routes) { - Vector<String> cidrRoutes = new Vector<String>(); + Vector<String> cidrRoutes = new Vector<>(); if (routes == null) { // No routes set, return empty vector return cidrRoutes; @@ -563,7 +583,7 @@ public class VpnProfile implements Serializable, Cloneable { if (!route.equals("")) { String cidrroute = cidrToIPAndNetmask(route); if (cidrroute == null) - return null; + return cidrRoutes; cidrRoutes.add(cidrroute); } @@ -573,7 +593,7 @@ public class VpnProfile implements Serializable, Cloneable { } private Collection<String> getCustomRoutesv6(String routes) { - Vector<String> cidrRoutes = new Vector<String>(); + Vector<String> cidrRoutes = new Vector<>(); if (routes == null) { // No routes set, return empty vector return cidrRoutes; @@ -606,47 +626,40 @@ public class VpnProfile implements Serializable, Cloneable { return null; - long nm = 0xffffffffl; - nm = (nm << (32 - len)) & 0xffffffffl; + 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; } - - 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; } @@ -657,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; @@ -670,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; @@ -682,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); @@ -698,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; @@ -708,7 +720,7 @@ public class VpnProfile implements Serializable, Cloneable { public VpnProfile copy(String name) { try { - VpnProfile copy = (VpnProfile) clone(); + VpnProfile copy = clone(); copy.mName = name; return copy; @@ -720,24 +732,21 @@ 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) { - PrivateKey privateKey = null; - X509Certificate[] caChain; - Exception exp; + synchronized String[] getKeyStoreCertificates(Context context, int tries) { try { - privateKey = KeyChain.getPrivateKey(context, mAlias); + PrivateKey privateKey = KeyChain.getPrivateKey(context, mAlias); mPrivateKey = privateKey; String keystoreChain = null; - caChain = KeyChain.getCertificateChain(context, mAlias); - if(caChain == null) + X509Certificate[] caChain = KeyChain.getCertificateChain(context, mAlias); + if (caChain == null) throw new NoCertReturnedException("No certificate returned from Keystore"); if (caChain.length <= 1 && TextUtils.isEmpty(mCaFilename)) { @@ -758,13 +767,14 @@ public class VpnProfile implements Serializable, Cloneable { String caout = null; if (!TextUtils.isEmpty(mCaFilename)) { try { - Certificate cacert = X509Utils.getCertificateFromFile(mCaFilename); + Certificate[] cacerts = X509Utils.getCertificatesFromFile(mCaFilename); StringWriter caoutWriter = new StringWriter(); PemWriter pw = new PemWriter(caoutWriter); - pw.writeObject(new PemObject("CERTIFICATE", cacert.getEncoded())); + 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()); @@ -787,54 +797,43 @@ 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}; - } 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 (InterruptedException | IOException | KeyChainException | NoCertReturnedException | IllegalArgumentException + | CertificateException e) { + e.printStackTrace(); + VpnStatus.logError(R.string.keyChainAccessError, e.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; + } 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); } - 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 + //! Return an error if something is wrong public int checkProfile(Context context) { if (mAuthenticationType == TYPE_KEYSTORE || mAuthenticationType == TYPE_USERPASS_KEYSTORE) { if (mAlias == null) @@ -845,15 +844,33 @@ public class VpnProfile implements Serializable, Cloneable { 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; + if (!mUseDefaultRoute) { + if (!TextUtils.isEmpty(mCustomRoutes) && getCustomRoutes(mCustomRoutes).size() == 0) + return R.string.custom_route_format_error; + + 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 @@ -948,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; @@ -980,7 +997,6 @@ public class VpnProfile implements Serializable, Cloneable { public String getSignedData(String b64data) { PrivateKey privkey = getKeystoreKey(); - Exception err; byte[] data = Base64.decode(b64data, Base64.DEFAULT); @@ -1004,26 +1020,14 @@ public class VpnProfile implements Serializable, Cloneable { byte[] signed_bytes = rsaSigner.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; + } catch (NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException + | BadPaddingException | NoSuchPaddingException e) { + VpnStatus.logError(R.string.error_rsa_sign, e.getClass().toString(), e.getLocalizedMessage()); + return null; } - - 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); @@ -1044,21 +1048,10 @@ public class VpnProfile implements Serializable, Cloneable { 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; + } catch (NoSuchMethodException | InvalidKeyException | InvocationTargetException | IllegalAccessException | IllegalArgumentException e) { + VpnStatus.logError(R.string.error_rsa_sign, e.getClass().toString(), e.getLocalizedMessage()); + return null; } - 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/BaseActivity.java b/app/src/main/java/de/blinkt/openvpn/activities/BaseActivity.java new file mode 100644 index 00000000..8cdc1e90 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/activities/BaseActivity.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2012-2015 Arne Schwabe + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt + */ + +package de.blinkt.openvpn.activities; + +import android.app.Activity; +import android.app.UiModeManager; +import android.content.res.Configuration; +import android.os.Bundle; +import android.view.Window; + +public class BaseActivity extends Activity { + private boolean isAndroidTV() { + final UiModeManager uiModeManager = (UiModeManager) getSystemService(Activity.UI_MODE_SERVICE); + return uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + if (isAndroidTV()) { + requestWindowFeature(Window.FEATURE_OPTIONS_PANEL); + } + super.onCreate(savedInstanceState); + } +} 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 dfd815e4..d25bccad 100644 --- a/app/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java +++ b/app/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2014 Arne Schwabe + * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ @@ -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/activities/LogWindow.java b/app/src/main/java/de/blinkt/openvpn/activities/LogWindow.java index 45f09c8e..130084f5 100644 --- a/app/src/main/java/de/blinkt/openvpn/activities/LogWindow.java +++ b/app/src/main/java/de/blinkt/openvpn/activities/LogWindow.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2014 Arne Schwabe + * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ @@ -15,7 +15,7 @@ import de.blinkt.openvpn.fragments.LogFragment; /** * Created by arne on 13.10.13. */ -public class LogWindow extends Activity { +public class LogWindow extends BaseActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); diff --git a/app/src/main/java/de/blinkt/openvpn/core/CIDRIP.java b/app/src/main/java/de/blinkt/openvpn/core/CIDRIP.java index 94ed8a0b..07f2152f 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/CIDRIP.java +++ b/app/src/main/java/de/blinkt/openvpn/core/CIDRIP.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2014 Arne Schwabe + * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ 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 232c454b..d14e643e 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java +++ b/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2014 Arne Schwabe + * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ @@ -32,11 +32,11 @@ 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 { + HashMap<String, String> optionAliases = new HashMap<>(); + optionAliases.put("server-poll-timeout", "timeout-connect"); BufferedReader br = new BufferedReader(reader); @@ -48,9 +48,15 @@ public class ConfigParser { if (line == null) break; - if (lineno == 1 && (line.startsWith("PK\003\004") - || (line.startsWith("PK\007\008")))) - throw new ConfigParseError("Input looks like a ZIP Archive. Import is only possible for OpenVPN config files (.ovpn/.conf)"); + if (lineno == 1) { + if ((line.startsWith("PK\003\004") + || (line.startsWith("PK\007\008")))) { + throw new ConfigParseError("Input looks like a ZIP Archive. Import is only possible for OpenVPN config files (.ovpn/.conf)"); + } + if (line.startsWith("\uFEFF")) { + line = line.substring(1); + } + } // Check for OpenVPN Access Server Meta information if (line.startsWith("# OVPN_ACCESS_SERVER_")) { @@ -70,6 +76,9 @@ public class ConfigParser { checkinlinefile(args, br); String optionname = args.get(0); + if (optionAliases.get(optionname)!=null) + optionname = optionAliases.get(optionname); + if (!options.containsKey(optionname)) { options.put(optionname, new Vector<Vector<String>>()); } @@ -121,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 @@ -137,7 +142,7 @@ public class ConfigParser { } - public class ConfigParseError extends Exception { + public static class ConfigParseError extends Exception { private static final long serialVersionUID = -60L; public ConfigParseError(String msg) { @@ -288,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 = { @@ -388,6 +394,10 @@ public class ConfigParser { np.mCustomRoutesv6 = customIPv6Routes; } + Vector<String> routeNoPull = getOption("route-nopull", 1, 1); + if (routeNoPull!=null) + np.mRoutenopull=true; + // Also recognize tls-auth [inline] direction ... Vector<Vector<String>> tlsauthoptions = getAllOption("tls-auth", 1, 2); if (tlsauthoptions != null) { @@ -567,6 +577,9 @@ public class ConfigParser { if (getOption("persist-tun", 0, 0) != null) np.mPersistTun = true; + if (getOption("push-peer-info", 0, 0) != null) + np.mPushPeerInfo = true; + Vector<String> connectretry = getOption("connect-retry", 1, 1); if (connectretry != null) np.mConnectRetry = connectretry.get(1); @@ -603,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); + } @@ -709,8 +723,18 @@ public class ConfigParser { conn.mUseUdp = isUdpProto(proto.get(1)); } + Vector<String> connectTimeout = getOption("connect-timeout", 1, 1); + if (connectTimeout != null) { + try { + conn.mConnectTimeout = Integer.parseInt(connectTimeout.get(1)); + } catch (NumberFormatException nfe) { + throw new ConfigParseError(String.format("Argument to connect-timeout (%s) must to be an integer: %s", + connectTimeout.get(1), nfe.getLocalizedMessage())); + + } + } - // Parse remote config + // Parse remote config Vector<Vector<String>> remotes = getAllOption("remote", 1, 3); @@ -785,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)) @@ -838,13 +852,21 @@ public class ConfigParser { return false; } + //! Generate options for custom options private String getOptionStrings(Vector<Vector<String>> option) { String custom = ""; for (Vector<String> optionsline : option) { if (!ignoreThisOption(optionsline)) { - for (String arg : optionsline) - custom += VpnProfile.openVpnEscape(arg) + " "; - custom += "\n"; + // Check if option had been inlined and inline again + if (optionsline.size() == 2 && "extra-certs".equals(optionsline.get(0)) ) { + custom += VpnProfile.insertFileData(optionsline.get(0), optionsline.get(1)); + + + } else { + for (String arg : optionsline) + custom += VpnProfile.openVpnEscape(arg) + " "; + custom += "\n"; + } } } return custom; diff --git a/app/src/main/java/de/blinkt/openvpn/core/Connection.java b/app/src/main/java/de/blinkt/openvpn/core/Connection.java index b10664ce..3455450b 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/Connection.java +++ b/app/src/main/java/de/blinkt/openvpn/core/Connection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2014 Arne Schwabe + * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ @@ -16,6 +16,7 @@ public class Connection implements Serializable, Cloneable { public String mCustomConfiguration=""; public boolean mUseCustomConfig=false; public boolean mEnabled=true; + public int mConnectTimeout = 0; private static final long serialVersionUID = 92031902903829089L; @@ -33,6 +34,10 @@ public class Connection implements Serializable, Cloneable { else cfg += " tcp-client\n"; + if (mConnectTimeout!=0) + cfg += String.format(" connect-timeout %d\n" , mConnectTimeout); + + if (!TextUtils.isEmpty(mCustomConfiguration) && mUseCustomConfig) { cfg += mCustomConfiguration; cfg += "\n"; 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 4ccf5472..40684af3 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java +++ b/app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2014 Arne Schwabe + * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ @@ -12,15 +12,19 @@ import android.content.SharedPreferences; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.NetworkInfo.State; +import android.os.Handler; import android.preference.PreferenceManager; + import se.leap.bitmaskclient.R; import de.blinkt.openvpn.core.VpnStatus.ByteCountListener; import java.util.LinkedList; +import java.util.Objects; import static de.blinkt.openvpn.core.OpenVPNManagement.pauseReason; -public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountListener { +public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountListener, OpenVPNManagement.PausedStateCallback { + private final Handler mDisconnectHandler; private int lastNetwork = -1; private OpenVPNManagement mManagement; @@ -29,12 +33,36 @@ public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountL // Data traffic limit in bytes private final long TRAFFIC_LIMIT = 64 * 1024; + // Time to wait after network disconnect to pause the VPN + private final int DISCONNECT_WAIT = 20; + connectState network = connectState.DISCONNECTED; connectState screen = connectState.SHOULDBECONNECTED; connectState userpause = connectState.SHOULDBECONNECTED; private String lastStateMsg = null; + private java.lang.Runnable mDelayDisconnectRunnable = new Runnable() { + @Override + public void run() { + if (!(network == connectState.PENDINGDISCONNECT)) + return; + + network = connectState.DISCONNECTED; + + // Set screen state to be disconnected if disconnect pending + if (screen == connectState.PENDINGDISCONNECT) + screen = connectState.DISCONNECTED; + + mManagement.pause(getPauseReason()); + } + }; + private NetworkInfo lastConnectedNetwork; + + @Override + public boolean shouldBeRunning() { + return shouldBeConnected(); + } enum connectState { SHOULDBECONNECTED, @@ -54,6 +82,7 @@ public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountL LinkedList<Datapoint> trafficdata = new LinkedList<DeviceStateReceiver.Datapoint>(); + @Override public void updateByteCount(long in, long out, long diffIn, long diffOut) { if (screen != connectState.PENDINGDISCONNECT) @@ -99,6 +128,8 @@ public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountL public DeviceStateReceiver(OpenVPNManagement magnagement) { super(); mManagement = magnagement; + mManagement.setPauseCallback(this); + mDisconnectHandler = new Handler(); } @@ -113,7 +144,7 @@ public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountL boolean screenOffPause = prefs.getBoolean("screenoff", false); if (screenOffPause) { - if (ProfileManager.getLastConnectedVpn()!=null && !ProfileManager.getLastConnectedVpn().mPersistTun) + if (ProfileManager.getLastConnectedVpn() != null && !ProfileManager.getLastConnectedVpn().mPersistTun) VpnStatus.logError(R.string.screen_nopersistenttun); screen = connectState.PENDINGDISCONNECT; @@ -126,6 +157,8 @@ public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountL boolean connected = shouldBeConnected(); screen = connectState.SHOULDBECONNECTED; + /* We should connect now, cancel any outstanding disconnect timer */ + mDisconnectHandler.removeCallbacks(mDelayDisconnectRunnable); /* should be connected has changed because the screen is on now, connect the VPN */ if (shouldBeConnected() != connected) mManagement.resume(); @@ -140,6 +173,10 @@ public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountL private void fillTrafficData() { trafficdata.add(new Datapoint(System.currentTimeMillis(), TRAFFIC_LIMIT)); } + public static boolean equalsObj(Object a, Object b) { + return (a == null) ? (b == null) : a.equals(b); + } + public void networkStateChange(Context context) { @@ -175,34 +212,49 @@ public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountL if (networkInfo != null && networkInfo.getState() == State.CONNECTED) { int newnet = networkInfo.getType(); + + boolean pendingDisconnect = (network == connectState.PENDINGDISCONNECT); network = connectState.SHOULDBECONNECTED; - if (lastNetwork != newnet) { + boolean sameNetwork; + if (lastConnectedNetwork == null + || lastConnectedNetwork.getType() != networkInfo.getType() + || !equalsObj(lastConnectedNetwork.getExtraInfo(), networkInfo.getExtraInfo()) + ) + sameNetwork = false; + else + sameNetwork = true; + + /* Same network, connection still 'established' */ + if (pendingDisconnect && sameNetwork) { + mDisconnectHandler.removeCallbacks(mDelayDisconnectRunnable); + // Reprotect the sockets just be sure + mManagement.networkChange(true); + } else { + /* Different network or connection not established anymore */ + if (screen == connectState.PENDINGDISCONNECT) screen = connectState.DISCONNECTED; if (shouldBeConnected()) { - if (lastNetwork == -1) { - mManagement.resume(); - } else { - mManagement.networkChange(); + mDisconnectHandler.removeCallbacks(mDelayDisconnectRunnable); - } + if (pendingDisconnect || !sameNetwork) + mManagement.networkChange(sameNetwork); + else + mManagement.resume(); } lastNetwork = newnet; + lastConnectedNetwork = networkInfo; } } 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; + network = connectState.PENDINGDISCONNECT; + mDisconnectHandler.postDelayed(mDelayDisconnectRunnable, DISCONNECT_WAIT * 1000); - mManagement.pause(getPauseReason()); } } @@ -213,6 +265,7 @@ public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountL } + public boolean isUserPaused() { return userpause == connectState.DISCONNECTED; } diff --git a/app/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java b/app/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java index 6e9e63c5..db3ae751 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java +++ b/app/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2014 Arne Schwabe + * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ @@ -37,5 +37,7 @@ public class ICSOpenVPNApplication extends Application { if (BuildConfig.DEBUG) { //ACRA.init(this); } + + VpnStatus.initLogCache(getApplicationContext().getCacheDir()); } } diff --git a/app/src/main/java/de/blinkt/openvpn/core/LogFileHandler.java b/app/src/main/java/de/blinkt/openvpn/core/LogFileHandler.java new file mode 100644 index 00000000..288c7934 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/core/LogFileHandler.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2012-2015 Arne Schwabe + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt + */ + +package de.blinkt.openvpn.core; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.Parcel; + +import java.io.BufferedInputStream; +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. + */ +class LogFileHandler extends Handler { + static final int TRIM_LOG_FILE = 100; + static final int FLUSH_TO_DISK = 101; + static final int LOG_INIT = 102; + public static final int LOG_MESSAGE = 103; + private static FileOutputStream mLogFile; + + public static final String LOGFILE_NAME = "logcache.dat"; + + + public LogFileHandler(Looper looper) { + super(looper); + } + + + @Override + 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) { + // Ignore log messages if not yet initialized + if (mLogFile == null) + return; + writeLogItemToDisk((VpnStatus.LogItem) msg.obj); + } else if (msg.what == TRIM_LOG_FILE) { + trimLogFile(); + for (VpnStatus.LogItem li : VpnStatus.getlogbuffer()) + writeLogItemToDisk(li); + } else if (msg.what == FLUSH_TO_DISK) { + flushToDisk(); + } + + } catch (IOException e) { + e.printStackTrace(); + VpnStatus.logError("Error during log cache: " + msg.what); + VpnStatus.logException(e); + } + + } + + private void flushToDisk() throws IOException { + mLogFile.flush(); + } + + private static void trimLogFile() { + try { + 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); + // We do not really care if the log cache breaks between Android upgrades, + // write binary format to disc + byte[] liBytes = p.marshall(); + + byte[] lenBytes = ByteBuffer.allocate(4).putInt(liBytes.length).array(); + mLogFile.write(lenBytes); + mLogFile.write(liBytes); + p.recycle(); + } + + private void openLogFile (File cacheDir) throws FileNotFoundException { + File logfile = new File(cacheDir, LOGFILE_NAME); + mLogFile = new FileOutputStream(logfile); + } + + private void readLogCache(File cacheDir) { + File logfile = new File(cacheDir, LOGFILE_NAME); + + + if (!logfile.exists() || !logfile.canRead()) + return; + + + + try { + + BufferedInputStream logFile = new BufferedInputStream(new FileInputStream(logfile)); + + byte[] buf = new byte[8192]; + int read = logFile.read(buf, 0, 4); + int itemsRead=0; + + 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); + 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, 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"); + VpnStatus.logException(e); + e.printStackTrace(); + // ignore reading file error + } + } + + 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/LogItem.java b/app/src/main/java/de/blinkt/openvpn/core/LogItem.java new file mode 100644 index 00000000..6aefbb2e --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/core/LogItem.java @@ -0,0 +1,377 @@ +/* + * Copyright (c) 2012-2016 Arne Schwabe + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt + */ + +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.Signature; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; +import android.util.Log; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +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.Locale; +import java.util.UnknownFormatConversionException; + +import se.leap.bitmaskclient.R; + +/** + * Created by arne on 24.04.16. + */ +public class LogItem implements Parcelable { + private Object[] mArgs = null; + private String mMessage = null; + private int mRessourceId; + // Default log priority + VpnStatus.LogLevel mLevel = VpnStatus.LogLevel.INFO; + private long logtime = System.currentTimeMillis(); + private int mVerbosityLevel = -1; + + private LogItem(int ressourceId, Object[] args) { + mRessourceId = ressourceId; + mArgs = args; + } + + public LogItem(VpnStatus.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); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof LogItem)) + return obj.equals(this); + LogItem other = (LogItem) obj; + + return Arrays.equals(mArgs, other.mArgs) && + ((other.mMessage == null && mMessage == other.mMessage) || + mMessage.equals(other.mMessage)) && + mRessourceId == other.mRessourceId && + ((mLevel == null && other.mLevel == mLevel) || + other.mLevel.equals(mLevel)) && + mVerbosityLevel == other.mVerbosityLevel && + logtime == other.logtime; + + + } + + public byte[] getMarschaledBytes() throws UnsupportedEncodingException { + ByteBuffer bb = ByteBuffer.allocate(16384); + + + bb.put((byte) 0x0); //version + bb.putLong(logtime); //8 + bb.putInt(mVerbosityLevel); //4 + bb.putInt(mLevel.getInt()); + bb.putInt(mRessourceId); + if (mMessage == null || mMessage.length() == 0) { + bb.putInt(0); + } else { + marschalString(mMessage, bb); + } + if (mArgs == null || mArgs.length == 0) { + bb.putInt(0); + } else { + bb.putInt(mArgs.length); + for (Object o : mArgs) { + if (o instanceof String) { + bb.putChar('s'); + marschalString((String) o, bb); + } else if (o instanceof Integer) { + bb.putChar('i'); + bb.putInt((Integer) o); + } else if (o instanceof Float) { + bb.putChar('f'); + bb.putFloat((Float) o); + } else if (o instanceof Double) { + bb.putChar('d'); + bb.putDouble((Double) o); + } else if (o instanceof Long) { + bb.putChar('l'); + bb.putLong((Long) o); + } else if (o == null) { + bb.putChar('0'); + } else { + VpnStatus.logDebug("Unknown object for LogItem marschaling " + o); + bb.putChar('s'); + marschalString(o.toString(), bb); + } + + } + } + + int pos = bb.position(); + bb.rewind(); + return Arrays.copyOf(bb.array(), pos); + + } + + public LogItem(byte[] in, int length) throws UnsupportedEncodingException { + ByteBuffer bb = ByteBuffer.wrap(in, 0, length); + bb.get(); // ignore version + logtime = bb.getLong(); + mVerbosityLevel = bb.getInt(); + mLevel = VpnStatus.LogLevel.getEnumByValue(bb.getInt()); + mRessourceId = bb.getInt(); + int len = bb.getInt(); + if (len == 0) { + mMessage = null; + } else { + if (len > bb.remaining()) + throw new IndexOutOfBoundsException("String length " + len + " is bigger than remaining bytes " + bb.remaining()); + byte[] utf8bytes = new byte[len]; + bb.get(utf8bytes); + mMessage = new String(utf8bytes, "UTF-8"); + } + int numArgs = bb.getInt(); + if (numArgs > 30) { + throw new IndexOutOfBoundsException("Too many arguments for Logitem to unmarschal"); + } + if (numArgs == 0) { + mArgs = null; + } else { + mArgs = new Object[numArgs]; + for (int i = 0; i < numArgs; i++) { + char type = bb.getChar(); + switch (type) { + case 's': + mArgs[i] = unmarschalString(bb); + break; + case 'i': + mArgs[i] = bb.getInt(); + break; + case 'd': + mArgs[i] = bb.getDouble(); + break; + case 'f': + mArgs[i] = bb.getFloat(); + break; + case 'l': + mArgs[i] = bb.getLong(); + break; + case '0': + mArgs[i] = null; + break; + default: + throw new UnsupportedEncodingException("Unknown format type: " + type); + } + } + } + if (bb.hasRemaining()) + throw new UnsupportedEncodingException(bb.remaining() + " bytes left after unmarshaling everything"); + } + + private void marschalString(String str, ByteBuffer bb) throws UnsupportedEncodingException { + byte[] utf8bytes = str.getBytes("UTF-8"); + bb.putInt(utf8bytes.length); + bb.put(utf8bytes); + } + + private String unmarschalString(ByteBuffer bb) throws UnsupportedEncodingException { + int len = bb.getInt(); + byte[] utf8bytes = new byte[len]; + bb.get(utf8bytes); + return new String(utf8bytes, "UTF-8"); + } + + + public LogItem(Parcel in) { + mArgs = in.readArray(Object.class.getClassLoader()); + mMessage = in.readString(); + mRessourceId = in.readInt(); + mLevel = VpnStatus.LogLevel.getEnumByValue(in.readInt()); + mVerbosityLevel = in.readInt(); + logtime = in.readLong(); + } + + public static final Creator<LogItem> CREATOR + = new Creator<LogItem>() { + public LogItem createFromParcel(Parcel in) { + return new LogItem(in); + } + + public LogItem[] newArray(int size) { + return new LogItem[size]; + } + }; + + public LogItem(VpnStatus.LogLevel loglevel, int ressourceId, Object... args) { + mRessourceId = ressourceId; + mArgs = args; + mLevel = loglevel; + } + + + public LogItem(VpnStatus.LogLevel loglevel, String msg) { + mLevel = loglevel; + mMessage = msg; + } + + + public LogItem(VpnStatus.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) + str += join("|", mArgs); + + 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; + } + + } + + + // TextUtils.join will cause not macked exeception in tests .... + public static String join(CharSequence delimiter, Object[] tokens) { + StringBuilder sb = new StringBuilder(); + boolean firstTime = true; + for (Object token : tokens) { + if (firstTime) { + firstTime = false; + } else { + sb.append(delimiter); + } + sb.append(token); + } + return sb.toString(); + } + + + public VpnStatus.LogLevel getLogLevel() { + return mLevel; + } + + + @Override + public String toString() { + return getString(null); + } + + // 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 { + @SuppressLint("PackageManagerGetSignatures") + 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, VpnStatus.officalkey)) + apksign = c.getString(R.string.official_build); + else if (Arrays.equals(digest, VpnStatus.officaldebugkey)) + apksign = c.getString(R.string.debug_build); + else if (Arrays.equals(digest, VpnStatus.amazonkey)) + apksign = "amazon version"; + else if (Arrays.equals(digest, VpnStatus.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 (PackageManager.NameNotFoundException | CertificateException | + NoSuchAlgorithmException ignored) { + } + + Object[] argsext = Arrays.copyOf(mArgs, mArgs.length); + argsext[argsext.length - 1] = apksign; + argsext[argsext.length - 2] = version; + + return c.getString(R.string.mobile_info, 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 boolean verify() { + if (mLevel == null) + return false; + + if (mMessage == null && mRessourceId == 0) + return false; + + return true; + } +} diff --git a/app/src/main/java/de/blinkt/openvpn/core/LollipopDeviceStateListener.java b/app/src/main/java/de/blinkt/openvpn/core/LollipopDeviceStateListener.java index 440458e4..04e4e8b4 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/LollipopDeviceStateListener.java +++ b/app/src/main/java/de/blinkt/openvpn/core/LollipopDeviceStateListener.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2014 Arne Schwabe + * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ diff --git a/app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java b/app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java index f67b7730..ea003d41 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java +++ b/app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java @@ -1,19 +1,26 @@ /* - * Copyright (c) 2012-2014 Arne Schwabe + * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; +import android.os.Build; + import java.security.InvalidKeyException; public class NativeUtils { - public static native byte[] rsasign(byte[] input,int pkey) throws InvalidKeyException; - public static native String[] getIfconfig() throws IllegalArgumentException; - static native void jniclose(int fdint); - - static { - System.loadLibrary("stlport_shared"); - System.loadLibrary("opvpnutil"); - } + public static native byte[] rsasign(byte[] input, int pkey) throws InvalidKeyException; + + public static native String[] getIfconfig() throws IllegalArgumentException; + + static native void jniclose(int fdint); + + public static native String getNativeAPI(); + + static { + System.loadLibrary("opvpnutil"); + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) + System.loadLibrary("jbcrypto"); + } } 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 c86f9e44..eb6d4d42 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java +++ b/app/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java @@ -1,28 +1,28 @@ /* - * Copyright (c) 2012-2014 Arne Schwabe + * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; import android.os.Build; +import android.support.annotation.NonNull; import android.text.TextUtils; import junit.framework.Assert; -import org.jetbrains.annotations.NotNull; - import java.math.BigInteger; import java.net.Inet6Address; -import java.util.*; +import java.util.Collection; +import java.util.Locale; +import java.util.PriorityQueue; +import java.util.TreeSet; +import java.util.Vector; import se.leap.bitmaskclient.BuildConfig; public class NetworkSpace { - - - static class ipAddress implements Comparable<ipAddress> { private BigInteger netAddress; public int networkMask; @@ -38,7 +38,7 @@ public class NetworkSpace { * 2. smaller networks are returned as smaller */ @Override - public int compareTo(@NotNull ipAddress another) { + public int compareTo(@NonNull ipAddress another) { int comp = getFirstAddress().compareTo(another.getFirstAddress()); if (comp != 0) return comp; @@ -159,16 +159,20 @@ public class NetworkSpace { String getIPv6Address() { if (BuildConfig.DEBUG) Assert.assertTrue (!isV4); BigInteger r = netAddress; - if (r.compareTo(BigInteger.ZERO)==0 && networkMask==0) - return "::"; Vector<String> parts = new Vector<String>(); - while (r.compareTo(BigInteger.ZERO) == 1) { - parts.add(0, String.format(Locale.US, "%x", r.mod(BigInteger.valueOf(0x10000)).longValue())); + while (r.compareTo(BigInteger.ZERO) == 1 || parts.size() <3) { + long part = r.mod(BigInteger.valueOf(0x10000)).longValue(); + if (part!=0) + parts.add(0, String.format(Locale.US, "%x", part)); + else + parts.add(0, ""); r = r.shiftRight(16); } - - return TextUtils.join(":", parts); + String ipv6str = TextUtils.join(":", parts); + while (ipv6str.contains(":::")) + ipv6str = ipv6str.replace(":::", "::"); + return ipv6str; } public boolean containsNet(ipAddress network) { 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 1f28c77d..2911fb1e 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java @@ -1,29 +1,39 @@ /* - * Copyright (c) 2012-2014 Arne Schwabe + * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; public interface OpenVPNManagement { + interface PausedStateCallback { + boolean shouldBeRunning(); + } + enum pauseReason { noNetwork, userPause, 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 */ - void networkChange(); + void networkChange(boolean sameNetwork); + + void setPauseCallback(PausedStateCallback callback); } 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 f9cb9a86..2917bce1 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2014 Arne Schwabe + * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ @@ -10,15 +10,18 @@ import android.annotation.TargetApi; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; +import android.app.UiModeManager; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageManager; +import android.content.res.Configuration; import android.net.ConnectivityManager; import android.net.VpnService; import android.os.Binder; import android.os.Build; +import android.os.Handler; import android.os.Handler.Callback; import android.os.IBinder; import android.os.Message; @@ -27,7 +30,9 @@ import android.preference.PreferenceManager; import android.system.OsConstants; 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; @@ -81,6 +86,9 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac private String mLastTunCfg; private String mRemoteGW; 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) { @@ -109,7 +117,8 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac @Override public void onRevoke() { - mManagement.stopVPN(); + VpnStatus.logInfo(R.string.permission_revoked); + mManagement.stopVPN(false); endVpnService(); } @@ -125,6 +134,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac VpnStatus.removeByteCountListener(this); unregisterDeviceStateReceiver(); ProfileManager.setConntectedVpnProfileDisconnected(this); + mOpenVPNThread = null; if (!mStarting) { stopForeground(!mNotificationAlwaysVisible); @@ -135,7 +145,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } } - private void showNotification(String msg, String tickerText, boolean lowpriority, long when, ConnectionStatus status) { + private void showNotification(final String msg, String tickerText, boolean lowpriority, long when, ConnectionStatus status) { String ns = Context.NOTIFICATION_SERVICE; NotificationManager mNotificationManager = (NotificationManager) getSystemService(ns); @@ -164,6 +174,9 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) jbNotificationExtras(lowpriority, nbuilder); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + lpNotificationExtras(nbuilder); + if (tickerText != null && !tickerText.equals("")) nbuilder.setTicker(tickerText); @@ -172,7 +185,34 @@ 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) + guiHandler.post(new Runnable() { + + @Override + public void run() { + + if (mlastToast != null) + mlastToast.cancel(); + String toastText = String.format(Locale.getDefault(), "%s - %s", mProfile.mName, msg); + mlastToast = Toast.makeText(getBaseContext(), toastText, Toast.LENGTH_SHORT); + mlastToast.show(); + } + }); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + private void lpNotificationExtras(Notification.Builder nbuilder) { + nbuilder.setCategory(Notification.CATEGORY_SERVICE); + nbuilder.setLocalOnly(true); + + } + + private boolean runningOnAndroidTV() { + UiModeManager uiModeManager = (UiModeManager) getSystemService(UI_MODE_SERVICE); + return uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION; } private int getIconByConnectionStatus(ConnectionStatus level) { @@ -215,20 +255,20 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac disconnectVPN.setAction(DISCONNECT_VPN); PendingIntent disconnectPendingIntent = PendingIntent.getActivity(this, 0, disconnectVPN, 0); - nbuilder.addAction(android.R.drawable.ic_menu_close_clear_cancel, + nbuilder.addAction(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, + nbuilder.addAction(R.drawable.ic_menu_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, + nbuilder.addAction(R.drawable.ic_menu_play, getString(R.string.resumevpn), resumeVPNPending); } @@ -297,6 +337,9 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac VpnStatus.addStateListener(this); VpnStatus.addByteCountListener(this); + guiHandler = new Handler(getMainLooper()); + + if (intent != null && PAUSE_VPN.equals(intent.getAction())) { if (mDeviceStateReceiver != null) mDeviceStateReceiver.userPause(true); @@ -338,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; @@ -380,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)) { @@ -393,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; @@ -409,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() { @@ -435,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; @@ -445,7 +528,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac public void onDestroy() { synchronized (mProcessLock) { if (mProcessThread != null) { - mManagement.stopVPN(); + mManagement.stopVPN(true); } } @@ -454,6 +537,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } // Just in case unregister for state VpnStatus.removeStateListener(this); + VpnStatus.flushLog(); } @@ -536,6 +620,26 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac Collection<ipAddress> positiveIPv4Routes = mRoutes.getPositiveIPList(); Collection<ipAddress> positiveIPv6Routes = mRoutesv6.getPositiveIPList(); + if ("samsung".equals(Build.BRAND) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && mDnslist.size() >= 1) { + // 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; + for (ipAddress net : positiveIPv4Routes) { + if (net.containsNet(dnsServer)) { + dnsIncluded = true; + } + } + if (!dnsIncluded) { + String samsungwarning = String.format("Warning Samsung Android 5.0+ devices ignore DNS servers outside the VPN range. To enable DNS resolution a route to your DNS Server (%s) has been added.", mDnslist.get(0)); + VpnStatus.logWarning(samsungwarning); + positiveIPv4Routes.add(dnsServer); + } + } catch (Exception e) { + VpnStatus.logError("Error parsing DNS Server IP: " + mDnslist.get(0)); + } + } + ipAddress multicastRange = new ipAddress(new CIDRIP("224.0.0.0", 3), true); for (NetworkSpace.ipAddress route : positiveIPv4Routes) { @@ -558,25 +662,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } } - if ("samsung".equals(Build.BRAND) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && mDnslist.size() >= 1) { - // 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; - for (ipAddress net : positiveIPv4Routes) { - if (net.containsNet(dnsServer)) { - dnsIncluded = true; - } - } - if (!dnsIncluded) { - String samsungwarning = String.format("Warning Samsung Android 5.0+ devices ignore DNS servers outside the VPN range. To enable DNS add a custom route to your DNS Server (%s) or change to a DNS inside your VPN range", mDnslist.get(0)); - VpnStatus.logWarning(samsungwarning); - } - } catch (Exception e) { - VpnStatus.logError("Error parsing DNS Server IP: " + mDnslist.get(0)); - } - } - if (mDomain != null) builder.addSearchDomain(mDomain); @@ -672,12 +757,14 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac @TargetApi(Build.VERSION_CODES.LOLLIPOP) private void setAllowedVpnPackages(Builder builder) { + boolean atLeastOneAllowedApp = false; for (String pkg : mProfile.mAllowedAppsVpn) { try { if (mProfile.mAllowedAppsVpnAreDisallowed) { builder.addDisallowedApplication(pkg); } else { builder.addAllowedApplication(pkg); + atLeastOneAllowedApp = true; } } catch (PackageManager.NameNotFoundException e) { mProfile.mAllowedAppsVpn.remove(pkg); @@ -685,6 +772,15 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } } + if (!mProfile.mAllowedAppsVpnAreDisallowed && !atLeastOneAllowedApp) { + VpnStatus.logDebug(R.string.no_allowed_app, getPackageName()); + try { + builder.addAllowedApplication(getPackageName()); + } catch (PackageManager.NameNotFoundException e) { + VpnStatus.logError("This should not happen: " + e.getLocalizedMessage()); + } + } + if (mProfile.mAllowedAppsVpnAreDisallowed) { VpnStatus.logDebug(R.string.disallowed_vpn_apps_info, TextUtils.join(", ", mProfile.mAllowedAppsVpn)); } else { @@ -839,7 +935,9 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } else if (level == LEVEL_CONNECTED) { mDisplayBytecount = true; mConnecttime = System.currentTimeMillis(); - lowpriority = true; + if (!runningOnAndroidTV()) + lowpriority = true; + String ns = Context.NOTIFICATION_SERVICE; NotificationManager mNotificationManager = (NotificationManager) getSystemService(ns); mNotificationManager.cancel(OPENVPN_STATUS); @@ -852,7 +950,8 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac // CONNECTED // Does not work :( String msg = getString(resid); - // showNotification(msg + " " + logmessage, msg, lowpriority, 0, level); + // showNotification(VpnStatus.getLastCleanLogMessage(this), + // 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 d856feb7..e0c39546 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2014 Arne Schwabe + * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ @@ -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 1c3b3362..569a3846 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java @@ -1,23 +1,20 @@ /* - * Copyright (c) 2012-2014 Arne Schwabe + * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ 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.support.annotation.NonNull; import android.util.Log; import junit.framework.Assert; -import org.jetbrains.annotations.NotNull; - import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStream; @@ -42,30 +39,24 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { private LocalSocket mSocket; private VpnProfile mProfile; private OpenVPNService mOpenVPNService; - private LinkedList<FileDescriptor> mFDList = new LinkedList<FileDescriptor>(); + private LinkedList<FileDescriptor> mFDList = new LinkedList<>(); private LocalServerSocket mServerSocket; - private boolean mReleaseHold = true; private boolean mWaitingForRelease = false; private long mLastHoldRelease = 0; - private static final Vector<OpenVpnManagementThread> active = new Vector<OpenVpnManagementThread>(); + private static final Vector<OpenVpnManagementThread> active = new Vector<>(); private LocalSocket mServerSocketLocal; private pauseReason lastPauseReason = pauseReason.noNetwork; + private PausedStateCallback mPauseCallback; + private boolean mShuttingDown; 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) { + public boolean openManagementInterface(@NonNull Context c) { // Could take a while to open connection int tries = 8; @@ -74,7 +65,7 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { mServerSocketLocal = new LocalSocket(); - while (tries > 0 && !mServerSocketLocal.isConnected()) { + while (tries > 0 && !mServerSocketLocal.isBound()) { try { mServerSocketLocal.bind(new LocalSocketAddress(socketName, LocalSocketAddress.Namespace.FILESYSTEM)); @@ -82,7 +73,7 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { // wait 300 ms before retrying try { Thread.sleep(300); - } catch (InterruptedException e1) { + } catch (InterruptedException ignored) { } } @@ -167,7 +158,6 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { //! Hack O Rama 2000! private void protectFileDescriptor(FileDescriptor fd) { - Exception exp; try { Method getInt = FileDescriptor.class.getDeclaredMethod("getInt$"); int fdint = (Integer) getInt.invoke(fd); @@ -183,20 +173,12 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { //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; + } catch (NoSuchMethodException | IllegalArgumentException | InvocationTargetException | IllegalAccessException | NullPointerException e) { + VpnStatus.logException("Failed to retrieve fd from socket (" + fd + ")", 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) { @@ -225,31 +207,42 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { String argument = parts[1]; - if (cmd.equals("INFO")) { + switch (cmd) { + case "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); + return; + case "PASSWORD": + processPWCommand(argument); + break; + case "HOLD": + handleHold(); + break; + case "NEED-OK": + processNeedCommand(argument); + break; + case "BYTECOUNT": + processByteCount(argument); + break; + case "STATE": + if (!mShuttingDown) + processState(argument); + break; + case "PROXY": + processProxyCMD(argument); + break; + case "LOG": + processLogMessage(argument); + break; + case "RSA_SIGN": + processSignCommand(argument); + break; + default: + VpnStatus.logWarning("MGMT: Got unrecognized command" + command); + Log.i(TAG, "Got unrecognized command" + command); + break; } } else if (command.startsWith("SUCCESS:")) { - /* Ignore this kind of message too */ + /* Ignore this kind of message too */ return; } else if (command.startsWith("PROTECTFD: ")) { FileDescriptor fdtoprotect = mFDList.pollFirst(); @@ -278,16 +271,22 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { 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; + switch (args[1]) { + case "I": + level = VpnStatus.LogLevel.INFO; + break; + case "W": + level = VpnStatus.LogLevel.WARNING; + break; + case "D": + level = VpnStatus.LogLevel.VERBOSE; + break; + case "F": + level = VpnStatus.LogLevel.ERROR; + break; + default: + level = VpnStatus.LogLevel.INFO; + break; } int ovpnlevel = Integer.parseInt(args[2]) & 0x0F; @@ -299,8 +298,15 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { VpnStatus.logMessageOpenVPN(level, ovpnlevel, msg); } + boolean shouldBeRunning() { + if (mPauseCallback == null) + return false; + else + return mPauseCallback.shouldBeRunning(); + } + private void handleHold() { - if (mReleaseHold) { + if (shouldBeRunning()) { releaseHoldCmd(); } else { mWaitingForRelease = true; @@ -327,11 +333,10 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { //managmentCommand("log on all\n"); } + public void releaseHold() { - mReleaseHold = true; if (mWaitingForRelease) releaseHoldCmd(); - } private void processProxyCMD(String argument) { @@ -391,15 +396,19 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { 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(" "); + switch (needed) { + case "PROTECTFD": + FileDescriptor fdtoprotect = mFDList.pollFirst(); + protectFileDescriptor(fdtoprotect); + break; + case "DNSSERVER": + mOpenVPNService.addDNS(extra); + break; + case "DNSDOMAIN": + mOpenVPNService.setDomain(extra); + break; + case "ROUTE": { + String[] routeparts = extra.split(" "); /* buf_printf (&out, "%s %s %s dev %s", network, netmask, gateway, rgi->iface); @@ -407,38 +416,46 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { buf_printf (&out, "%s %s %s", network, netmask, gateway); */ - if (routeparts.length == 5) { - if (BuildConfig.DEBUG) Assert.assertEquals("dev", routeparts[3]); - 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); + if (routeparts.length == 5) { + if (BuildConfig.DEBUG) Assert.assertEquals("dev", routeparts[3]); + 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); + } + + break; + } + case "ROUTE6": { + String[] routeparts = extra.split(" "); + mOpenVPNService.addRoutev6(routeparts[0], routeparts[1]); + break; } + case "IFCONFIG": + String[] ifconfigparts = extra.split(" "); + int mtu = Integer.parseInt(ifconfigparts[2]); + mOpenVPNService.setLocalIP(ifconfigparts[0], ifconfigparts[1], mtu, ifconfigparts[3]); + break; + case "IFCONFIG6": + mOpenVPNService.setLocalIPv6(extra); + + break; + case "PERSIST_TUN_ACTION": + // check if tun cfg stayed the same + status = mOpenVPNService.getTunReopenStatus(); + break; + case "OPENTUN": + if (sendTunFD(needed, extra)) + return; + else + status = "cancel"; + // This not nice or anything but setFileDescriptors accepts only FilDescriptor class :( - } 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)) + break; + default: + Log.e(TAG, "Unknown needok command " + argument); 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); @@ -446,7 +463,6 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { } 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)); @@ -480,18 +496,10 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { 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; + } catch (NoSuchMethodException | IllegalArgumentException | InvocationTargetException | + IOException | IllegalAccessException exp) { + VpnStatus.logException("Could not send fd over socket", exp); } - VpnStatus.logException("Could not send fd over socket", exp); return false; } @@ -559,14 +567,21 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { } @Override - public void networkChange() { - if (!mWaitingForRelease) + public void networkChange(boolean samenetwork) { + if (mWaitingForRelease) + releaseHold(); + else if (samenetwork) + managmentCommand("network-change samenetwork\n"); + else managmentCommand("network-change\n"); } - public void signalusr1() { - mReleaseHold = false; + @Override + public void setPauseCallback(PausedStateCallback callback) { + mPauseCallback = callback; + } + public void signalusr1() { if (!mWaitingForRelease) managmentCommand("signal SIGUSR1\n"); else @@ -608,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/PRNGFixes.java b/app/src/main/java/de/blinkt/openvpn/core/PRNGFixes.java index a788426a..49a7eaa9 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/PRNGFixes.java +++ b/app/src/main/java/de/blinkt/openvpn/core/PRNGFixes.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2014 Arne Schwabe + * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ diff --git a/app/src/main/java/de/blinkt/openvpn/core/ProfileManager.java b/app/src/main/java/de/blinkt/openvpn/core/ProfileManager.java index 086cdb44..4f9c219b 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/ProfileManager.java +++ b/app/src/main/java/de/blinkt/openvpn/core/ProfileManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2014 Arne Schwabe + * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ @@ -128,6 +128,11 @@ public class ProfileManager { ProfileManager.tmpprofile = tmp; } + public static boolean isTempProfile() + { + return mLastConnectedVpn == tmpprofile; + } + public void saveProfile(Context context, VpnProfile profile) { ObjectOutputStream vpnfile; 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 6e2abb13..34fb69f8 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/ProxyDetection.java +++ b/app/src/main/java/de/blinkt/openvpn/core/ProxyDetection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2014 Arne Schwabe + * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ 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 47cb633c..78f462e7 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java +++ b/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2014 Arne Schwabe + * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ @@ -14,30 +14,38 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.Arrays; import java.util.Vector; import se.leap.bitmaskclient.R; import de.blinkt.openvpn.VpnProfile; public class VPNLaunchHelper { - private static final String MININONPIEVPN = "nopievpn"; - private static final String MINIPIEVPN = "pievpn"; + private static final String MININONPIEVPN = "nopie_openvpn"; + private static final String MINIPIEVPN = "pie_openvpn"; private static final String OVPNCONFIGFILE = "android.conf"; - 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(); + abis = getSupportedABIsLollipop(); else + //noinspection deprecation abis = new String[]{Build.CPU_ABI, Build.CPU_ABI2}; + String nativeAPI = NativeUtils.getNativeAPI(); + if (!nativeAPI.equals(abis[0])) { + VpnStatus.logWarning(R.string.abi_mismatch, Arrays.toString(abis), nativeAPI); + abis = new String[] {nativeAPI}; + } + for (String abi: abis) { - File mvpnout = new File(context.getCacheDir(), getMiniVPNExecutableName() + "." + abi); - if ((mvpnout.exists() && mvpnout.canExecute()) || writeMiniVPNBinary(context, abi, mvpnout)) { - return mvpnout.getPath(); + File vpnExecutable = new File(context.getCacheDir(), getMiniVPNExecutableName() + "." + abi); + if ((vpnExecutable.exists() && vpnExecutable.canExecute()) || writeMiniVPNBinary(context, abi, vpnExecutable)) { + return vpnExecutable.getPath(); } } @@ -45,7 +53,7 @@ public class VPNLaunchHelper { } @TargetApi(Build.VERSION_CODES.LOLLIPOP) - private static String[] getSupportedAbisLollipop() { + private static String[] getSupportedABIsLollipop() { return Build.SUPPORTED_ABIS; } @@ -65,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)); @@ -118,13 +132,6 @@ public class VPNLaunchHelper { public static void startOpenVpn(VpnProfile startprofile, Context context) { - if(writeMiniVPN(context)==null) { - VpnStatus.logError("Error writing minivpn binary"); - return; - } - - VpnStatus.logInfo(R.string.building_configration); - 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 62a60643..1e2ccba3 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java +++ b/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java @@ -1,24 +1,28 @@ /* - * Copyright (c) 2012-2014 Arne Schwabe + * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; import android.annotation.SuppressLint; -import android.app.Activity; 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.HandlerThread; +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; @@ -33,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 { @@ -72,7 +77,59 @@ 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) { + String message = mLaststatemsg; + switch (mLastLevel) { + case LEVEL_CONNECTED: + String[] parts = mLaststatemsg.split(","); + /* + (a) the integer unix date/time, + (b) the state name, + 0 (c) optional descriptive string (used mostly on RECONNECTING + and EXITING to show the reason for the disconnect), + + 1 (d) optional TUN/TAP local IPv4 address + 2 (e) optional address of remote server, + 3 (f) optional port of remote server, + 4 (g) optional local address, + 5 (h) optional local port, and + 6 (i) optional TUN/TAP local IPv6 address. +*/ + // Return only the assigned IP addresses in the UI + if (parts.length >= 7) + message = String.format(Locale.US, "%s %s", parts[1], parts[6]); + break; + } + + while (message.endsWith(",")) + message = message.substring(0, message.length() - 1); + + String status = mLaststate; + if (status.equals("NOPROCESS")) + return message; + + String prefix = c.getString(mLastStateresid); + if (mLastStateresid == R.string.unknown_state) + message = status + message; + if (message.length() > 0) + prefix += ": "; + + return prefix + message; + + } + + public static void initLogCache(File cacheDir) { + Message m = mLogFileHandler.obtainMessage(LogFileHandler.LOG_INIT, cacheDir); + mLogFileHandler.sendMessage(m); + + } + + public static void flushLog() { + mLogFileHandler.sendEmptyMessage(LogFileHandler.FLUSH_TO_DISK); + } public enum ConnectionStatus { LEVEL_CONNECTED, @@ -81,6 +138,7 @@ public class VpnStatus { LEVEL_CONNECTING_NO_SERVER_REPLY_YET, LEVEL_NONETWORK, LEVEL_NOTCONNECTED, + LEVEL_START, LEVEL_AUTH_FAILED, LEVEL_WAITING_FOR_USER_INPUT, UNKNOWN_LEVEL @@ -128,18 +186,24 @@ public class VpnStatus { private static ConnectionStatus mLastLevel = ConnectionStatus.LEVEL_NOTCONNECTED; + private static final LogFileHandler mLogFileHandler; + static { logbuffer = new LinkedList<>(); logListener = new Vector<>(); stateListener = new Vector<>(); byteCountListener = new Vector<>(); + + HandlerThread mHandlerThread = new HandlerThread("LogFileWriter", Thread.MIN_PRIORITY); + mHandlerThread.start(); + mLogFileHandler = new LogFileHandler(mHandlerThread.getLooper()); + logInformation(); + } public static class LogItem implements Parcelable { - - private Object[] mArgs = null; private String mMessage = null; private int mRessourceId; @@ -231,7 +295,6 @@ public class VpnStatus { if (mArgs != null) str += TextUtils.join("|", mArgs); - return str; } } @@ -261,6 +324,7 @@ public class VpnStatus { String version = "error getting version"; try { + @SuppressLint("PackageManagerGetSignatures") 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())); @@ -287,11 +351,11 @@ public class VpnStatus { NoSuchAlgorithmException ignored) { } - Object[] argsext = Arrays.copyOf(mArgs, mArgs.length + 2); + Object[] argsext = Arrays.copyOf(mArgs, mArgs.length); argsext[argsext.length - 1] = apksign; argsext[argsext.length - 2] = version; - return c.getString(R.string.mobile_info_extended, argsext); + return c.getString(R.string.mobile_info, argsext); } @@ -308,12 +372,16 @@ public class VpnStatus { } return mVerbosityLevel; } - } - public void saveLogToDisk(Context c) { + public boolean verify() { + if (mLevel == null) + return false; - File logOut = new File(c.getCacheDir(), "log.xml"); + if (mMessage == null && mRessourceId == 0) + return false; + return true; + } } public interface LogListener { @@ -336,10 +404,12 @@ public class VpnStatus { public synchronized static void clearLog() { logbuffer.clear(); logInformation(); + mLogFileHandler.sendEmptyMessage(LogFileHandler.TRIM_LOG_FILE); } private static void logInformation() { - logInfo(R.string.mobile_info, Build.MODEL, Build.BOARD, Build.BRAND, Build.VERSION.SDK_INT); + logInfo(R.string.mobile_info, Build.MODEL, Build.BOARD, Build.BRAND, Build.VERSION.SDK_INT, + NativeUtils.getNativeAPI(), Build.VERSION.RELEASE, Build.ID, Build.FINGERPRINT, "", ""); } public synchronized static void addLogListener(LogListener ll) { @@ -369,32 +439,34 @@ public class VpnStatus { } 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; + switch (state) { + case "CONNECTING": + return R.string.state_connecting; + case "WAIT": + return R.string.state_wait; + case "AUTH": + return R.string.state_auth; + case "GET_CONFIG": + return R.string.state_get_config; + case "ASSIGN_IP": + return R.string.state_assign_ip; + case "ADD_ROUTES": + return R.string.state_add_routes; + case "CONNECTED": + return R.string.state_connected; + case "DISCONNECTED": + return R.string.state_disconnected; + case "RECONNECTING": + return R.string.state_reconnecting; + case "EXITING": + return R.string.state_exiting; + case "RESOLVE": + return R.string.state_resolve; + case "TCP_CONNECT": + return R.string.state_tcp_connect; + default: + return R.string.unknown_state; + } } @@ -496,17 +568,36 @@ public class VpnStatus { newLogItem(new LogItem(LogLevel.DEBUG, resourceId, args)); } + private static void newLogItem(LogItem logItem) { + newLogItem(logItem, false); + } + + + synchronized static void newLogItem(LogItem logItem, boolean cachedLine) { + if (cachedLine) { + logbuffer.addFirst(logItem); + } else { + logbuffer.addLast(logItem); + Message m = mLogFileHandler.obtainMessage(LogFileHandler.LOG_MESSAGE, logItem); + mLogFileHandler.sendMessage(m); + } + + if (logbuffer.size() > MAXLOGENTRIES + MAXLOGENTRIES / 2) { + while (logbuffer.size() > MAXLOGENTRIES) + logbuffer.removeFirst(); + mLogFileHandler.sendMessage(mLogFileHandler.obtainMessage(LogFileHandler.TRIM_LOG_FILE)); + } + + if (BuildConfig.DEBUG && !cachedLine) + Log.d("OpenVPN", logItem.getString(null)); - 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)); @@ -538,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/core/X509Utils.java b/app/src/main/java/de/blinkt/openvpn/core/X509Utils.java index 0786967b..4048f0e0 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/X509Utils.java +++ b/app/src/main/java/de/blinkt/openvpn/core/X509Utils.java @@ -1,11 +1,12 @@ /* - * Copyright (c) 2012-2014 Arne Schwabe + * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; import android.content.Context; +import android.content.res.Resources; import android.text.TextUtils; import se.leap.bitmaskclient.R; @@ -20,30 +21,39 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.security.cert.Certificate; import java.security.cert.CertificateException; +import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateFactory; +import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Date; import java.util.Hashtable; +import java.util.Vector; public class X509Utils { - public static Certificate getCertificateFromFile(String certfilename) throws FileNotFoundException, CertificateException { + public static Certificate[] getCertificatesFromFile(String certfilename) throws FileNotFoundException, CertificateException { CertificateFactory certFact = CertificateFactory.getInstance("X.509"); - InputStream inStream; - + Vector<Certificate> certificates = new Vector<>(); 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()); + do { + // The java certifcate reader is ... kind of stupid + // It does NOT ignore chars before the --BEGIN ... + subIndex = Math.max(0, subIndex); + InputStream inStream = new ByteArrayInputStream(certfilename.substring(subIndex).getBytes()); + certificates.add(certFact.generateCertificate(inStream)); + subIndex = certfilename.indexOf("-----BEGIN CERTIFICATE-----", subIndex+1); + } while (subIndex > 0); + return certificates.toArray(new Certificate[certificates.size()]); } else { - inStream = new FileInputStream(certfilename); + InputStream inStream = new FileInputStream(certfilename); + return new Certificate[] {certFact.generateCertificate(inStream)}; } - return certFact.generateCertificate(inStream); } public static PemObject readPemObjectFromFile (String keyfilename) throws IOException { @@ -67,9 +77,10 @@ public class X509Utils { public static String getCertificateFriendlyName (Context c, String filename) { if(!TextUtils.isEmpty(filename)) { try { - X509Certificate cert = (X509Certificate) getCertificateFromFile(filename); - - return getCertificateFriendlyName(cert); + X509Certificate cert = (X509Certificate) getCertificatesFromFile(filename)[0]; + String friendlycn = getCertificateFriendlyName(cert); + friendlycn = getCertificateValidityString(cert, c.getResources()) + friendlycn; + return friendlycn; } catch (Exception e) { VpnStatus.logError("Could not read certificate" + e.getLocalizedMessage()); @@ -78,6 +89,40 @@ public class X509Utils { return c.getString(R.string.cannotparsecert); } + public static String getCertificateValidityString(X509Certificate cert, Resources res) { + try { + cert.checkValidity(); + } catch (CertificateExpiredException ce) { + return "EXPIRED: "; + } catch (CertificateNotYetValidException cny) { + return "NOT YET VALID: "; + } + + Date certNotAfter = cert.getNotAfter(); + Date now = new Date(); + long timeLeft = certNotAfter.getTime() - now.getTime(); // Time left in ms + + // More than 72h left, display days + // More than 3 months display months + if (timeLeft > 90l* 24 * 3600 * 1000) { + long months = getMonthsDifference(now, certNotAfter); + return res.getString(R.string.months_left, months); + } else if (timeLeft > 72 * 3600 * 1000) { + long days = timeLeft / (24 * 3600 * 1000); + return res.getString(R.string.days_left, days); + } else { + long hours = timeLeft / (3600 * 1000); + + return res.getString(R.string.hours_left, hours); + } + } + + public static int getMonthsDifference(Date date1, Date date2) { + int m1 = date1.getYear() * 12 + date1.getMonth(); + int m2 = date2.getYear() * 12 + date2.getMonth(); + return m2 - m1 + 1; + } + public static String getCertificateFriendlyName(X509Certificate cert) { X500Principal principal = cert.getSubjectX500Principal(); byte[] encodedSubject = principal.getEncoded(); 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 2a75c15e..bbd52a34 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,5 @@ /* - * Copyright (c) 2012-2014 Arne Schwabe + * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ @@ -22,6 +22,9 @@ import android.os.Bundle; import android.os.Handler; import android.os.Handler.Callback; import android.os.Message; +import android.preference.Preference; +import android.preference.PreferenceManager; +import android.support.annotation.Nullable; import android.text.SpannableString; import android.text.format.DateFormat; import android.text.style.ImageSpan; @@ -33,6 +36,8 @@ import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemLongClickListener; +import android.widget.CheckBox; +import android.widget.CompoundButton; import android.widget.LinearLayout; import android.widget.ListAdapter; import android.widget.ListView; @@ -41,8 +46,6 @@ import android.widget.SeekBar; import android.widget.TextView; import android.widget.Toast; -import org.jetbrains.annotations.Nullable; - import java.text.SimpleDateFormat; import java.util.Collections; import java.util.Date; @@ -67,11 +70,12 @@ import static de.blinkt.openvpn.core.OpenVPNService.humanReadableByteCount; import se.leap.bitmaskclient.Dashboard; public class LogFragment extends ListFragment implements StateListener, SeekBar.OnSeekBarChangeListener, RadioGroup.OnCheckedChangeListener, VpnStatus.ByteCountListener { - private static final String LOGTIMEFORMAT = "logtimeformat"; - private static final int START_VPN_CONFIG = 0; + private static final String LOGTIMEFORMAT = "logtimeformat"; + private static final int START_VPN_CONFIG = 0; private static final String VERBOSITYLEVEL = "verbositylevel"; + private SeekBar mLogLevelSlider; private LinearLayout mOptionsLayout; private RadioGroup mTimeRadioGroup; @@ -79,10 +83,11 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar. private TextView mDownStatus; private TextView mConnectStatus; private boolean mShowOptionsLayout; + private CheckBox mClearLogCheckBox; @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - ladapter.setLogLevel(progress+1); + ladapter.setLogLevel(progress + 1); } @Override @@ -134,7 +139,7 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar. private static final int MESSAGE_NEWLOG = 0; - private static final int MESSAGE_CLEARLOG = 1; + private static final int MESSAGE_CLEARLOG = 1; private static final int MESSAGE_NEWTS = 2; private static final int MESSAGE_NEWLOGLEVEL = 3; @@ -144,110 +149,109 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar. public static final int TIME_FORMAT_ISO = 2; private static final int MAX_STORED_LOG_ENTRIES = 1000; - private Vector<LogItem> allEntries=new Vector<LogItem>(); + private Vector<LogItem> allEntries = new Vector<>(); - private Vector<LogItem> currentLevelEntries=new Vector<LogItem>(); + private Vector<LogItem> currentLevelEntries = new Vector<LogItem>(); - private Handler mHandler; + private Handler mHandler; - private Vector<DataSetObserver> observers=new Vector<DataSetObserver>(); + private Vector<DataSetObserver> observers = new Vector<DataSetObserver>(); - private int mTimeFormat=0; - private int mLogLevel=3; + private int mTimeFormat = 0; + private int mLogLevel = 3; public LogWindowListAdapter() { - initLogBuffer(); - if (mHandler == null) { - mHandler = new Handler(this); - } - - VpnStatus.addLogListener(this); - } + initLogBuffer(); + if (mHandler == null) { + mHandler = new Handler(this); + } + VpnStatus.addLogListener(this); + } - private void initLogBuffer() { - allEntries.clear(); + 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 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; + 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; - } + 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) + if (time == TIME_FORMAT_ISO) timeformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); else timeformat = DateFormat.getTimeFormat(getActivity()); @@ -289,49 +293,49 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar. } @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)) + 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(); } @@ -340,25 +344,25 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar. observer.onInvalidated(); } initLogBuffer(); - } else if (msg.what == MESSAGE_NEWTS) { - for (DataSetObserver observer : observers) { - observer.onInvalidated(); - } - } else if (msg.what == MESSAGE_NEWLOGLEVEL) { + } else if (msg.what == MESSAGE_NEWTS) { + for (DataSetObserver observer : observers) { + observer.onInvalidated(); + } + } else if (msg.what == MESSAGE_NEWLOGLEVEL) { initCurrentMessages(); - for (DataSetObserver observer: observers) { + for (DataSetObserver observer : observers) { observer.onChanged(); } } - return true; - } + return true; + } private void initCurrentMessages() { currentLevelEntries.clear(); - for(LogItem li: allEntries) { + for (LogItem li : allEntries) { if (li.getVerbosityLevel() <= mLogLevel || mLogLevel == VpnProfile.MAXLOGLEVEL) currentLevelEntries.add(li); @@ -366,7 +370,6 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar. } /** - * * @param logmessage * @return True if the current entries have changed */ @@ -376,7 +379,7 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar. if (allEntries.size() > MAX_STORED_LOG_ENTRIES) { Vector<LogItem> oldAllEntries = allEntries; allEntries = new Vector<LogItem>(allEntries.size()); - for (int i=50;i<oldAllEntries.size();i++) { + for (int i = 50; i < oldAllEntries.size(); i++) { allEntries.add(oldAllEntries.elementAt(i)); } initCurrentMessages(); @@ -392,85 +395,80 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar. } void clearLog() { - // Actually is probably called from GUI Thread as result of the user - // pressing a button. But better safe than sorry - VpnStatus.clearLog(); - VpnStatus.logInfo(R.string.logCleared); - mHandler.sendEmptyMessage(MESSAGE_CLEARLOG); - } - + // Actually is probably called from GUI Thread as result of the user + // pressing a button. But better safe than sorry + VpnStatus.clearLog(); + VpnStatus.logInfo(R.string.logCleared); + mHandler.sendEmptyMessage(MESSAGE_CLEARLOG); + } - public void setTimeFormat(int newTimeFormat) { - mTimeFormat= newTimeFormat; - mHandler.sendEmptyMessage(MESSAGE_NEWTS); - } + public void setTimeFormat(int newTimeFormat) { + mTimeFormat = newTimeFormat; + mHandler.sendEmptyMessage(MESSAGE_NEWTS); + } public void setLogLevel(int logLevel) { mLogLevel = logLevel; mHandler.sendEmptyMessage(MESSAGE_NEWLOGLEVEL); } - - } - - - private LogWindowListAdapter ladapter; - private TextView mSpeedView; + } + private LogWindowListAdapter ladapter; + private TextView mSpeedView; @Override - public boolean onOptionsItemSelected(MenuItem item) { - if(item.getItemId()==R.id.clearlog) { - ladapter.clearLog(); - return true; - } else if(item.getItemId()==R.id.cancel){ - Intent intent = new Intent(getActivity(),DisconnectVPN.class); + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.clearlog) { + ladapter.clearLog(); + return true; + } else if (item.getItemId() == R.id.cancel) { + 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.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() { boolean optionsVisible = (mOptionsLayout.getVisibility() != View.GONE); ObjectAnimator anim; if (optionsVisible) { - anim = ObjectAnimator.ofFloat(mOptionsLayout,"alpha",1.0f, 0f); + anim = ObjectAnimator.ofFloat(mOptionsLayout, "alpha", 1.0f, 0f); anim.addListener(collapseListener); } else { mOptionsLayout.setVisibility(View.VISIBLE); - anim = ObjectAnimator.ofFloat(mOptionsLayout,"alpha", 0f, 1.0f); + anim = ObjectAnimator.ofFloat(mOptionsLayout, "alpha", 0f, 1.0f); //anim = new TranslateAnimation(0.0f, 0.0f, mOptionsLayout.getHeight(), 0.0f); } @@ -493,16 +491,16 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar. @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.logmenu, menu); + inflater.inflate(R.menu.logmenu, menu); if (getResources().getBoolean(R.bool.logSildersAlwaysVisible)) menu.removeItem(R.id.toggle_time); - } + } - @Override + @Override public void onResume() { - super.onResume(); - VpnStatus.addStateListener(this); + super.onResume(); + VpnStatus.addStateListener(this); VpnStatus.addByteCountListener(this); Intent intent = new Intent(getActivity(), OpenVPNService.class); intent.setAction(OpenVPNService.START_SERVICE); @@ -512,45 +510,45 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar. @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == START_VPN_CONFIG && resultCode== Activity.RESULT_OK) { - String configuredVPN = data.getStringExtra(VpnProfile.EXTRA_PROFILEUUID); - - final VpnProfile profile = ProfileManager.get(getActivity(),configuredVPN); - ProfileManager.getInstance(getActivity()).saveProfile(getActivity(), profile); - // Name could be modified, reset List adapter - - AlertDialog.Builder dialog = new AlertDialog.Builder(getActivity()); - dialog.setTitle(R.string.configuration_changed); - dialog.setMessage(R.string.restart_vpn_after_change); - - - dialog.setPositiveButton(R.string.restart, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - Intent intent = new Intent(getActivity(), LaunchVPN.class); - intent.putExtra(LaunchVPN.EXTRA_KEY, profile.getUUIDString()); - intent.setAction(Intent.ACTION_MAIN); - startActivity(intent); - } - - - }); - dialog.setNegativeButton(R.string.ignore, null); - dialog.create().show(); - } - super.onActivityResult(requestCode, resultCode, data); - } + if (requestCode == START_VPN_CONFIG && resultCode == Activity.RESULT_OK) { + String configuredVPN = data.getStringExtra(VpnProfile.EXTRA_PROFILEUUID); + + final VpnProfile profile = ProfileManager.get(getActivity(), configuredVPN); + ProfileManager.getInstance(getActivity()).saveProfile(getActivity(), profile); + // Name could be modified, reset List adapter + + AlertDialog.Builder dialog = new AlertDialog.Builder(getActivity()); + dialog.setTitle(R.string.configuration_changed); + dialog.setMessage(R.string.restart_vpn_after_change); + + + dialog.setPositiveButton(R.string.restart, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Intent intent = new Intent(getActivity(), LaunchVPN.class); + intent.putExtra(LaunchVPN.EXTRA_KEY, profile.getUUIDString()); + intent.setAction(Intent.ACTION_MAIN); + startActivity(intent); + } + + + }); + dialog.setNegativeButton(R.string.ignore, null); + dialog.create().show(); + } + super.onActivityResult(requestCode, resultCode, data); + } @Override public void onStop() { - super.onStop(); - VpnStatus.removeStateListener(this); + super.onStop(); + VpnStatus.removeStateListener(this); VpnStatus.removeByteCountListener(this); getActivity().getPreferences(0).edit().putInt(LOGTIMEFORMAT, ladapter.mTimeFormat) - .putInt(VERBOSITYLEVEL, ladapter.mLogLevel).apply(); + .putInt(VERBOSITYLEVEL, ladapter.mLogLevel).apply(); } @@ -567,7 +565,7 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar. int position, long id) { ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText("Log Entry",((TextView) view).getText()); + 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; @@ -583,8 +581,8 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar. setHasOptionsMenu(true); ladapter = new LogWindowListAdapter(); - ladapter.mTimeFormat = getActivity().getPreferences(0).getInt(LOGTIMEFORMAT, 0); - int logLevel = getActivity().getPreferences(0).getInt(VERBOSITYLEVEL, 0); + ladapter.mTimeFormat = getActivity().getPreferences(0).getInt(LOGTIMEFORMAT, 1); + int logLevel = getActivity().getPreferences(0).getInt(VERBOSITYLEVEL, 1); ladapter.setLogLevel(logLevel); setListAdapter(ladapter); @@ -592,7 +590,7 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar. mTimeRadioGroup = (RadioGroup) v.findViewById(R.id.timeFormatRadioGroup); mTimeRadioGroup.setOnCheckedChangeListener(this); - if(ladapter.mTimeFormat== LogWindowListAdapter.TIME_FORMAT_ISO) { + 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); @@ -600,16 +598,25 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar. mTimeRadioGroup.check(R.id.radioShort); } + mClearLogCheckBox = (CheckBox) v.findViewById(R.id.clearlogconnect); + mClearLogCheckBox.setChecked(PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean(LaunchVPN.CLEARLOG, true)); + mClearLogCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + PreferenceManager.getDefaultSharedPreferences(getActivity()).edit().putBoolean(LaunchVPN.CLEARLOG, isChecked).apply(); + } + }); + 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.setMax(VpnProfile.MAXLOGLEVEL - 1); + mLogLevelSlider.setProgress(logLevel - 1); mLogLevelSlider.setOnSeekBarChangeListener(this); - if(getResources().getBoolean(R.bool.logSildersAlwaysVisible)) + if (getResources().getBoolean(R.bool.logSildersAlwaysVisible)) mOptionsLayout.setVisibility(View.VISIBLE); mUpStatus = (TextView) v.findViewById(R.id.speedUp); @@ -623,16 +630,16 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar. @Override public void onAttach(Activity activity) { super.onAttach(activity); - if(getResources().getBoolean(R.bool.logSildersAlwaysVisible)) { - mShowOptionsLayout=true; - if (mOptionsLayout!= null) + if (getResources().getBoolean(R.bool.logSildersAlwaysVisible)) { + mShowOptionsLayout = true; + if (mOptionsLayout != null) mOptionsLayout.setVisibility(View.VISIBLE); } } @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); //getActionBar().setDisplayHomeAsUpEnabled(true); @@ -642,19 +649,16 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar. @Override public void updateState(final String status, final String logMessage, final int resId, final ConnectionStatus level) { if (isAdded()) { + final String cleanLogMessage = VpnStatus.getLastCleanLogMessage(getActivity()); + 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 (mSpeedView != null) { + mSpeedView.setText(cleanLogMessage); + } if (mConnectStatus != null) mConnectStatus.setText(getString(resId)); } @@ -664,10 +668,10 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar. } - @Override + @Override public void onDestroy() { - VpnStatus.removeLogListener(ladapter); - super.onDestroy(); - } + VpnStatus.removeLogListener(ladapter); + super.onDestroy(); + } } diff --git a/app/src/main/java/de/blinkt/openvpn/views/SeekBarTicks.java b/app/src/main/java/de/blinkt/openvpn/views/SeekBarTicks.java index 82378b00..347ce708 100644 --- a/app/src/main/java/de/blinkt/openvpn/views/SeekBarTicks.java +++ b/app/src/main/java/de/blinkt/openvpn/views/SeekBarTicks.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2014 Arne Schwabe + * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ @@ -42,10 +42,9 @@ public class SeekBarTicks extends SeekBar { 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)); + //noinspection deprecation + mTickPaint.setColor( context.getResources().getColor(android.R.color.black)); a.recycle(); } diff --git a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java index bdc36e89..dbdf0a51 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java +++ b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java @@ -14,6 +14,22 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +/** + * Copyright (c) 2013 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ package se.leap.bitmaskclient; import android.annotation.*; @@ -31,6 +47,7 @@ import org.json.*; import java.net.*; import butterknife.*; +import de.blinkt.openvpn.core.VpnStatus; import se.leap.bitmaskclient.eip.*; import se.leap.bitmaskclient.userstatus.*; @@ -71,35 +88,55 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - app = this; - - PRNGFixes.apply(); - 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 diff --git a/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java b/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java index 2e3d7524..9210c6ec 100644 --- a/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java @@ -244,10 +244,7 @@ public class VpnFragment extends Fragment implements Observer { Context context = dashboard.getApplicationContext(); String error = eip_status.lastError(5, context); - if (!error.isEmpty()) { - dashboard.showLog(); - VoidVpnService.stop(); - } + if (!error.isEmpty()) VoidVpnService.stop(); updateIcon(); updateButton(); } |