diff options
Diffstat (limited to 'main/src/main/java')
24 files changed, 749 insertions, 262 deletions
diff --git a/main/src/main/java/de/blinkt/openvpn/LaunchVPN.java b/main/src/main/java/de/blinkt/openvpn/LaunchVPN.java index 86eef35a..90ea053a 100644 --- a/main/src/main/java/de/blinkt/openvpn/LaunchVPN.java +++ b/main/src/main/java/de/blinkt/openvpn/LaunchVPN.java @@ -73,6 +73,7 @@ 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_START_REASON = "de.blinkt.openvpn.start_reason"; public static final String CLEARLOG = "clearlogconnect"; @@ -85,6 +86,7 @@ public class LaunchVPN extends Activity { private boolean mCmfixed = false; private String mTransientAuthPW; private String mTransientCertOrPCKS12PW; + private String mSelectedProfileReason; @Override public void onCreate(Bundle icicle) { @@ -99,7 +101,6 @@ public class LaunchVPN extends Activity { IServiceStatus service = IServiceStatus.Stub.asInterface(binder); try { if (mTransientAuthPW != null) - service.setCachedPassword(mSelectedProfile.getUUIDString(), PasswordCache.AUTHPASSWORD, mTransientAuthPW); if (mTransientCertOrPCKS12PW != null) service.setCachedPassword(mSelectedProfile.getUUIDString(), PasswordCache.PCKS12ORCERTPASSWORD, mTransientCertOrPCKS12PW); @@ -126,38 +127,41 @@ public class LaunchVPN extends Activity { final String action = intent.getAction(); // If the intent is a request to create a shortcut, we'll do that and exit + if (!Intent.ACTION_MAIN.equals(action)) { + return; + } + // Check if we need to clear the log + if (Preferences.getDefaultSharedPreferences(this).getBoolean(CLEARLOG, true)) + VpnStatus.clearLog(); - if (Intent.ACTION_MAIN.equals(action)) { - // Check if we need to clear the log - if (Preferences.getDefaultSharedPreferences(this).getBoolean(CLEARLOG, true)) - VpnStatus.clearLog(); - - // we got called to be the starting point, most likely a shortcut - String shortcutUUID = intent.getStringExtra(EXTRA_KEY); - String shortcutName = intent.getStringExtra(EXTRA_NAME); - mhideLog = intent.getBooleanExtra(EXTRA_HIDELOG, false); + // we got called to be the starting point, most likely a shortcut + String shortcutUUID = intent.getStringExtra(EXTRA_KEY); + String shortcutName = intent.getStringExtra(EXTRA_NAME); + String startReason = intent.getStringExtra(EXTRA_START_REASON); + mhideLog = intent.getBooleanExtra(EXTRA_HIDELOG, false); - VpnProfile profileToConnect = ProfileManager.get(this, shortcutUUID); - if (shortcutName != null && profileToConnect == null) { - profileToConnect = ProfileManager.getInstance(this).getProfileByName(shortcutName); - if (!(new ExternalAppDatabase(this).checkRemoteActionPermission(this, getCallingPackage()))) { - finish(); - return; - } + VpnProfile profileToConnect = ProfileManager.get(this, shortcutUUID); + if (shortcutName != null && profileToConnect == null) { + profileToConnect = ProfileManager.getInstance(this).getProfileByName(shortcutName); + if (!(new ExternalAppDatabase(this).checkRemoteActionPermission(this, getCallingPackage()))) { + finish(); + return; } + } - if (profileToConnect == null) { - VpnStatus.logError(R.string.shortcut_profile_notfound); - // show Log window to display error - showLogWindow(); - finish(); - } else { - mSelectedProfile = profileToConnect; - launchVPN(); - } + if (profileToConnect == null) { + VpnStatus.logError(R.string.shortcut_profile_notfound); + // show Log window to display error + showLogWindow(); + finish(); + } else { + mSelectedProfile = profileToConnect; + mSelectedProfileReason = startReason; + launchVPN(); } + } private void askForPW(final int type) { @@ -251,7 +255,7 @@ public class LaunchVPN extends Activity { if (!mhideLog && showLogWindow) showLogWindow(); ProfileManager.updateLRU(this, mSelectedProfile); - VPNLaunchHelper.startOpenVpn(mSelectedProfile, getBaseContext()); + VPNLaunchHelper.startOpenVpn(mSelectedProfile, getBaseContext(), mSelectedProfileReason); finish(); } } else if (resultCode == Activity.RESULT_CANCELED) { diff --git a/main/src/main/java/de/blinkt/openvpn/OnBootReceiver.java b/main/src/main/java/de/blinkt/openvpn/OnBootReceiver.java index ebfab5b2..58c954c9 100644 --- a/main/src/main/java/de/blinkt/openvpn/OnBootReceiver.java +++ b/main/src/main/java/de/blinkt/openvpn/OnBootReceiver.java @@ -12,12 +12,10 @@ import android.content.SharedPreferences; import de.blinkt.openvpn.core.Preferences; import de.blinkt.openvpn.core.ProfileManager; +import de.blinkt.openvpn.core.VPNLaunchHelper; public class OnBootReceiver extends BroadcastReceiver { - - - // Debug: am broadcast -a android.intent.action.BOOT_COMPLETED @Override public void onReceive(Context context, Intent intent) { @@ -25,8 +23,8 @@ public class OnBootReceiver extends BroadcastReceiver { final String action = intent.getAction(); SharedPreferences prefs = Preferences.getDefaultSharedPreferences(context); - boolean useStartOnBoot = prefs.getBoolean("restartvpnonboot", false); - if (!useStartOnBoot) + boolean alwaysActive = prefs.getBoolean("restartvpnonboot", false); + if (!alwaysActive) return; if(Intent.ACTION_BOOT_COMPLETED.equals(action) || Intent.ACTION_MY_PACKAGE_REPLACED.equals(action)) { @@ -38,12 +36,6 @@ public class OnBootReceiver extends BroadcastReceiver { } void launchVPN(VpnProfile profile, Context context) { - Intent startVpnIntent = new Intent(Intent.ACTION_MAIN); - startVpnIntent.setClass(context, LaunchVPN.class); - startVpnIntent.putExtra(LaunchVPN.EXTRA_KEY,profile.getUUIDString()); - startVpnIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startVpnIntent.putExtra(LaunchVPN.EXTRA_HIDELOG, true); - - context.startActivity(startVpnIntent); + VPNLaunchHelper.startOpenVpn(profile, context.getApplicationContext(), "on Boot receiver"); } } diff --git a/main/src/main/java/de/blinkt/openvpn/VpnProfile.java b/main/src/main/java/de/blinkt/openvpn/VpnProfile.java index 3163bf27..0da09eb0 100644 --- a/main/src/main/java/de/blinkt/openvpn/VpnProfile.java +++ b/main/src/main/java/de/blinkt/openvpn/VpnProfile.java @@ -809,18 +809,6 @@ public class VpnProfile implements Serializable, Cloneable { 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; -// } - - return intent; - } - public void writeConfigFileOutput(Context context, OutputStream out) throws IOException { OutputStreamWriter cfg = new OutputStreamWriter(out); cfg.write(getConfigFile(context, false)); @@ -828,12 +816,14 @@ public class VpnProfile implements Serializable, Cloneable { cfg.close(); } - public Intent getStartServiceIntent(Context context) { + public Intent getStartServiceIntent(Context context, String startReason) { String prefix = context.getPackageName(); Intent intent = new Intent(context, OpenVPNService.class); intent.putExtra(prefix + ".profileUUID", mUuid.toString()); intent.putExtra(prefix + ".profileVersion", mVersion); + if (startReason != null) + intent.putExtra(prefix + ".startReason", startReason); return intent; } diff --git a/main/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java b/main/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java index 01dacfcd..6cc170fa 100644 --- a/main/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java +++ b/main/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java @@ -27,7 +27,7 @@ import de.blinkt.openvpn.core.VpnStatus; */ public class DisconnectVPN extends Activity implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener { private IOpenVPNServiceInternal mService; - private ServiceConnection mConnection = new ServiceConnection() { + private final ServiceConnection mConnection = new ServiceConnection() { @@ -86,6 +86,7 @@ public class DisconnectVPN extends Activity implements DialogInterface.OnClickLi } else if (which == DialogInterface.BUTTON_NEUTRAL) { Intent intent = new Intent(this, LaunchVPN.class); intent.putExtra(LaunchVPN.EXTRA_KEY, VpnStatus.getLastConnectedVPNProfile()); + intent.putExtra(LaunchVPN.EXTRA_START_REASON, "Reconnect button pressed."); intent.setAction(Intent.ACTION_MAIN); startActivity(intent); } diff --git a/main/src/main/java/de/blinkt/openvpn/api/AppRestrictions.java b/main/src/main/java/de/blinkt/openvpn/api/AppRestrictions.java index 175ecb70..53919216 100644 --- a/main/src/main/java/de/blinkt/openvpn/api/AppRestrictions.java +++ b/main/src/main/java/de/blinkt/openvpn/api/AppRestrictions.java @@ -14,7 +14,7 @@ import android.text.TextUtils; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.ConfigParser; -import de.blinkt.openvpn.core.Connection; +import de.blinkt.openvpn.core.OpenVPNService; import de.blinkt.openvpn.core.Preferences; import de.blinkt.openvpn.core.ProfileManager; import de.blinkt.openvpn.core.VpnStatus; @@ -22,6 +22,7 @@ import de.blinkt.openvpn.core.VpnStatus; import java.io.IOException; import java.io.StringReader; import java.math.BigInteger; +import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.*; @@ -33,16 +34,15 @@ public class AppRestrictions { final static int CONFIG_VERSION = 1; static boolean alreadyChecked = false; private static AppRestrictions mInstance; - private RestrictionsManager mRestrictionsMgr; private BroadcastReceiver mRestrictionsReceiver; - private AppRestrictions(Context c) { + private AppRestrictions() { } public static AppRestrictions getInstance(Context c) { if (mInstance == null) - mInstance = new AppRestrictions(c); + mInstance = new AppRestrictions(); return mInstance; } @@ -62,11 +62,12 @@ public class AppRestrictions { c.unregisterReceiver(mRestrictionsReceiver); } - private String hashConfig(String config) { + private String hashConfig(String rawconfig) { + String config = prepare(rawconfig); MessageDigest digest; try { digest = MessageDigest.getInstance("SHA1"); - byte utf8_bytes[] = config.getBytes(); + byte[] utf8_bytes = config.getBytes(StandardCharsets.UTF_8); digest.update(utf8_bytes, 0, utf8_bytes.length); return new BigInteger(1, digest.digest()).toString(16); } catch (NoSuchAlgorithmException e) { @@ -76,10 +77,14 @@ public class AppRestrictions { } private void applyRestrictions(Context c) { - mRestrictionsMgr = (RestrictionsManager) c.getSystemService(Context.RESTRICTIONS_SERVICE); - if (mRestrictionsMgr == null) + RestrictionsManager restrictionsMgr = (RestrictionsManager) c.getSystemService(Context.RESTRICTIONS_SERVICE); + if (restrictionsMgr == null) return; - Bundle restrictions = mRestrictionsMgr.getApplicationRestrictions(); + Bundle restrictions = restrictionsMgr.getApplicationRestrictions(); + parseRestrictionsBundle(c, restrictions); + } + public void parseRestrictionsBundle(Context c, Bundle restrictions) + { if (restrictions == null) return; @@ -94,12 +99,57 @@ public class AppRestrictions { VpnStatus.logError(String.format(Locale.US, "App restriction version %s does not match expected version %d", configVersion, CONFIG_VERSION)); return; } - Parcelable[] profileList = restrictions.getParcelableArray(("vpn_configuration_list")); + Parcelable[] profileList = restrictions.getParcelableArray("vpn_configuration_list"); if (profileList == null) { - VpnStatus.logError("App restriction does not contain a profile list (vpn_configuration_list)"); + VpnStatus.logInfo("App restriction does not contain a profile list. Removing previously added profiles. (vpn_configuration_list)"); + profileList = new Parcelable[]{}; + } + + importVPNProfiles(c, restrictions, profileList); + setAllowedRemoteControl(c, restrictions); + + setMiscSettings(c, restrictions); + } + + private void setAllowedRemoteControl(Context c, Bundle restrictions) { + String allowedApps = restrictions.getString("allowed_remote_access", null); + ExternalAppDatabase extapps = new ExternalAppDatabase(c); + + if (allowedApps == null) + { + extapps.setFlagManagedConfiguration(false); return; } + HashSet<String> restrictionApps = new HashSet<>(); + + for (String package_name:allowedApps.split("[, \n\r]")) { + if (!TextUtils.isEmpty(package_name)) { + restrictionApps.add(package_name); + } + } + extapps.setFlagManagedConfiguration(true); + extapps.clearAllApiApps(); + + if(!extapps.getExtAppList().equals(restrictionApps)) + { + extapps.setAllowedApps(restrictionApps); + } + } + + private static void setMiscSettings(Context c, Bundle restrictions) { + SharedPreferences defaultPrefs = Preferences.getDefaultSharedPreferences(c); + + if(restrictions.containsKey("screenoffpausevpn")) + { + boolean pauseVPN = restrictions.getBoolean("screenoffpausevpn"); + SharedPreferences.Editor editor = defaultPrefs.edit(); + editor.putBoolean("screenoff", pauseVPN); + editor.apply(); + } + } + + private void importVPNProfiles(Context c, Bundle restrictions, Parcelable[] profileList) { Set<String> provisionedUuids = new HashSet<>(); String defaultprofile = restrictions.getString("defaultprofile", null); @@ -116,12 +166,19 @@ public class AppRestrictions { String uuid = p.getString("uuid"); String ovpn = p.getString("ovpn"); String name = p.getString("name"); + String certAlias = p.getString("certificate_alias"); - if (uuid == null || ovpn == null || name == null) { + if (TextUtils.isEmpty(uuid) || TextUtils.isEmpty(ovpn) || TextUtils.isEmpty(name)) { VpnStatus.logError("App restriction profile misses uuid, ovpn or name key"); continue; } + /* we always use lower case uuid since Android UUID class will use present + * them that way */ + uuid = uuid.toLowerCase(Locale.US); + if (defaultprofile != null) + defaultprofile = defaultprofile.toLowerCase(Locale.US); + if (uuid.equals(defaultprofile)) defaultprofileProvisioned = true; @@ -134,12 +191,15 @@ public class AppRestrictions { if (vpnProfile != null) { // Profile exists, check if need to update it - if (ovpnHash.equals(vpnProfile.importedProfileHash)) + if (ovpnHash.equals(vpnProfile.importedProfileHash)) { + addCertificateAlias(vpnProfile, certAlias, c); + // not modified skip to next profile continue; - + } } - addProfile(c, ovpn, uuid, name, vpnProfile); + vpnProfile = addProfile(c, ovpn, uuid, name, vpnProfile); + addCertificateAlias(vpnProfile, certAlias, c); } Vector<VpnProfile> profilesToRemove = new Vector<>(); @@ -155,22 +215,79 @@ public class AppRestrictions { pm.removeProfile(c, vp); } + SharedPreferences defaultPrefs = Preferences.getDefaultSharedPreferences(c); + if (!TextUtils.isEmpty(defaultprofile)) { if (!defaultprofileProvisioned) { VpnStatus.logError("App restrictions: Setting a default profile UUID without providing a profile with that UUID"); } else { - SharedPreferences prefs = Preferences.getDefaultSharedPreferences(c); - String uuid = prefs.getString("alwaysOnVpn", null); + String uuid = defaultPrefs.getString("alwaysOnVpn", null); if (!defaultprofile.equals(uuid)) { - SharedPreferences.Editor editor = prefs.edit(); + SharedPreferences.Editor editor = defaultPrefs.edit(); editor.putString("alwaysOnVpn", defaultprofile); editor.apply(); + } } } } + /** + * If certAlias is non-null will modify the profile type to use the keystore variant of + * the authentication method and will also set the keystore alias + */ + private void addCertificateAlias(VpnProfile vpnProfile, String certAlias, Context c) { + if (vpnProfile == null) + return; + + if (certAlias == null) + certAlias = ""; + + int oldType = vpnProfile.mAuthenticationType; + String oldAlias = vpnProfile.mAlias; + + if (!TextUtils.isEmpty(certAlias)) { + switch (vpnProfile.mAuthenticationType) + { + case VpnProfile.TYPE_PKCS12: + case VpnProfile.TYPE_CERTIFICATES: + vpnProfile.mAuthenticationType = VpnProfile.TYPE_KEYSTORE; + break; + case VpnProfile.TYPE_USERPASS_CERTIFICATES: + case VpnProfile.TYPE_USERPASS_PKCS12: + vpnProfile.mAuthenticationType = VpnProfile.TYPE_USERPASS_KEYSTORE; + break; + } + + } else + { + /* Alias is null, return to non keystore method */ + boolean pkcs12present = !TextUtils.isEmpty(vpnProfile.mPKCS12Filename); + switch (vpnProfile.mAuthenticationType) { + case VpnProfile.TYPE_USERPASS_KEYSTORE: + if (pkcs12present) + vpnProfile.mAuthenticationType = VpnProfile.TYPE_USERPASS_PKCS12; + else + vpnProfile.mAuthenticationType = VpnProfile.TYPE_USERPASS_CERTIFICATES; + break; + case VpnProfile.TYPE_KEYSTORE: + if (pkcs12present) + vpnProfile.mAuthenticationType = VpnProfile.TYPE_PKCS12; + else + vpnProfile.mAuthenticationType = VpnProfile.TYPE_CERTIFICATES; + break; + } + } + vpnProfile.mAlias = certAlias; + + if (!certAlias.equals(oldAlias) || oldType != vpnProfile.mAuthenticationType) + { + ProfileManager pm = ProfileManager.getInstance(c); + pm.saveProfile(c, vpnProfile); + } + } + private String prepare(String config) { String newLine = System.getProperty("line.separator"); if (!config.contains(newLine) && !config.contains(" ")) { @@ -187,7 +304,7 @@ public class AppRestrictions { ; - private void addProfile(Context c, String config, String uuid, String name, VpnProfile vpnProfile) { + VpnProfile addProfile(Context c, String config, String uuid, String name, VpnProfile vpnProfile) { config = prepare(config); ConfigParser cp = new ConfigParser(); try { @@ -213,9 +330,11 @@ public class AppRestrictions { pm.addProfile(vp); pm.saveProfile(c, vp); pm.saveProfileList(c); + return vp; } catch (ConfigParser.ConfigParseError | IOException | IllegalArgumentException e) { VpnStatus.logException("Error during import of managed profile", e); + return null; } } diff --git a/main/src/main/java/de/blinkt/openvpn/api/ExternalAppDatabase.java b/main/src/main/java/de/blinkt/openvpn/api/ExternalAppDatabase.java index 57e82778..fe0afdb6 100644 --- a/main/src/main/java/de/blinkt/openvpn/api/ExternalAppDatabase.java +++ b/main/src/main/java/de/blinkt/openvpn/api/ExternalAppDatabase.java @@ -13,11 +13,13 @@ import android.content.SharedPreferences.Editor; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.os.Binder; +import android.widget.Toast; import java.util.HashSet; import java.util.Set; import de.blinkt.openvpn.core.Preferences; +import de.blinkt.openvpn.core.VpnStatus; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; @@ -30,12 +32,34 @@ public class ExternalAppDatabase { } private final static String PREFERENCES_KEY = "allowed_apps"; + private final static String PREFERENCES_KEY_MANAGED_CONFIG = "allowed_apps_managed"; + + public void setFlagManagedConfiguration(boolean managed) + { + SharedPreferences prefs = Preferences.getDefaultSharedPreferences(mContext); + Editor prefedit = prefs.edit(); + + prefedit.putBoolean(PREFERENCES_KEY_MANAGED_CONFIG, managed); + increaseWorkaroundCounter(prefs, prefedit); + prefedit.apply(); + } + + public boolean isManagedConfiguration() + { + SharedPreferences prefs = Preferences.getDefaultSharedPreferences(mContext); + return prefs.getBoolean(PREFERENCES_KEY_MANAGED_CONFIG, false); + } + + private static void increaseWorkaroundCounter(SharedPreferences prefs, Editor prefedit) { + // Workaround for bug + int counter = prefs.getInt("counter", 0); + prefedit.putInt("counter", counter + 1); + } boolean isAllowed(String packagename) { Set<String> allowedapps = getExtAppList(); - return allowedapps.contains(packagename); - + return allowedapps.contains(packagename); } public Set<String> getExtAppList() { @@ -50,14 +74,22 @@ public class ExternalAppDatabase { saveExtAppList(allowedapps); } + public boolean checkAllowingModifyingRemoteControl(Context c) { + if (isManagedConfiguration()) { + Toast.makeText(c, "Remote control apps are manged by managed configuration, cannot change", Toast.LENGTH_LONG).show(); + VpnStatus.logError("Remote control apps are manged by managed configuration, cannot change"); + return false; + } + return true; + } + private void saveExtAppList( Set<String> allowedapps) { SharedPreferences prefs = Preferences.getDefaultSharedPreferences(mContext); Editor prefedit = prefs.edit(); // Workaround for bug prefedit.putStringSet(PREFERENCES_KEY, allowedapps); - int counter = prefs.getInt("counter", 0); - prefedit.putInt("counter", counter + 1); + increaseWorkaroundCounter(prefs, prefedit); prefedit.apply(); } @@ -83,9 +115,9 @@ public class ExternalAppDatabase { } } catch (PackageManager.NameNotFoundException e) { // App not found. Remove it from the list - removeApp(appPackage); + if (!isManagedConfiguration()) + removeApp(appPackage); } - } throw new SecurityException("Unauthorized OpenVPN API Caller"); } @@ -105,4 +137,8 @@ public class ExternalAppDatabase { return false; } } + + public void setAllowedApps(Set<String> restrictionApps) { + saveExtAppList(restrictionApps); + } } diff --git a/main/src/main/java/de/blinkt/openvpn/api/ExternalOpenVPNService.java b/main/src/main/java/de/blinkt/openvpn/api/ExternalOpenVPNService.java index 690c349e..ab71f00b 100644 --- a/main/src/main/java/de/blinkt/openvpn/api/ExternalOpenVPNService.java +++ b/main/src/main/java/de/blinkt/openvpn/api/ExternalOpenVPNService.java @@ -139,16 +139,18 @@ public class ExternalOpenVPNService extends Service implements StateListener { * Check if we need to ask for username/password */
int neddPassword = vp.needUserPWInput(null, null);
+ String startReason = "external OpenVPN service by uid: " + Binder.getCallingUid();
if(vpnPermissionIntent != null || neddPassword != 0){
Intent shortVPNIntent = new Intent(Intent.ACTION_MAIN);
shortVPNIntent.setClass(getBaseContext(), de.blinkt.openvpn.LaunchVPN.class);
shortVPNIntent.putExtra(de.blinkt.openvpn.LaunchVPN.EXTRA_KEY, vp.getUUIDString());
shortVPNIntent.putExtra(de.blinkt.openvpn.LaunchVPN.EXTRA_HIDELOG, true);
+ shortVPNIntent.putExtra(de.blinkt.openvpn.LaunchVPN.EXTRA_START_REASON, startReason);
shortVPNIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(shortVPNIntent);
} else {
- VPNLaunchHelper.startOpenVpn(vp, getBaseContext());
+ VPNLaunchHelper.startOpenVpn(vp, getBaseContext(), startReason);
}
}
@@ -237,6 +239,8 @@ public class ExternalOpenVPNService extends Service implements StateListener { mExtAppDb.checkOpenVPNPermission(getPackageManager());
ProfileManager pm = ProfileManager.getInstance(getBaseContext());
VpnProfile vp = ProfileManager.get(getBaseContext(), profileUUID);
+ if (vp == null)
+ throw new RemoteException("Profile not found");
pm.removeProfile(ExternalOpenVPNService.this, vp);
}
@@ -244,7 +248,7 @@ public class ExternalOpenVPNService extends Service implements StateListener { public boolean protectSocket(ParcelFileDescriptor pfd) throws RemoteException {
mExtAppDb.checkOpenVPNPermission(getPackageManager());
try {
- boolean success= mService.protect(pfd.getFd());
+ boolean success = mService.protect(pfd.getFd());
pfd.close();
return success;
} catch (IOException e) {
@@ -339,7 +343,7 @@ public class ExternalOpenVPNService extends Service implements StateListener { - class UpdateMessage {
+ static class UpdateMessage {
public String state;
public String logmessage;
public ConnectionStatus level;
diff --git a/main/src/main/java/de/blinkt/openvpn/api/RemoteAction.java b/main/src/main/java/de/blinkt/openvpn/api/RemoteAction.java index 0554b88c..22110ad0 100644 --- a/main/src/main/java/de/blinkt/openvpn/api/RemoteAction.java +++ b/main/src/main/java/de/blinkt/openvpn/api/RemoteAction.java @@ -93,6 +93,7 @@ public class RemoteAction extends Activity { } else { Intent startVPN = new Intent(this, LaunchVPN.class); startVPN.putExtra(LaunchVPN.EXTRA_KEY, profile.getUUID().toString()); + startVPN.putExtra(LaunchVPN.EXTRA_START_REASON, ".api.ConnectVPN call"); startVPN.setAction(Intent.ACTION_MAIN); startActivity(startVPN); } diff --git a/main/src/main/java/de/blinkt/openvpn/core/ExtAuthHelper.java b/main/src/main/java/de/blinkt/openvpn/core/ExtAuthHelper.java index d102dce2..39151646 100644 --- a/main/src/main/java/de/blinkt/openvpn/core/ExtAuthHelper.java +++ b/main/src/main/java/de/blinkt/openvpn/core/ExtAuthHelper.java @@ -42,7 +42,6 @@ public class ExtAuthHelper { public static void setExternalAuthProviderSpinnerList(Spinner spinner, String selectedApp) { Context c = spinner.getContext(); - final PackageManager pm = c.getPackageManager(); ArrayList<ExternalAuthProvider> extProviders = getExternalAuthProviderList(c); int selectedPos = -1; diff --git a/main/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java b/main/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java index 1df46525..271ec139 100644 --- a/main/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java +++ b/main/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java @@ -128,5 +128,4 @@ public class ICSOpenVPNApplication extends Application { mChannel.setLightColor(Color.CYAN); mNotificationManager.createNotificationChannel(mChannel); } - } diff --git a/main/src/main/java/de/blinkt/openvpn/core/LocaleHelper.java b/main/src/main/java/de/blinkt/openvpn/core/LocaleHelper.java index 516e025d..ea9bf9e3 100644 --- a/main/src/main/java/de/blinkt/openvpn/core/LocaleHelper.java +++ b/main/src/main/java/de/blinkt/openvpn/core/LocaleHelper.java @@ -15,11 +15,23 @@ import java.util.Locale; public class LocaleHelper { static private Locale desiredLocale = null; - public static void setDesiredLocale(Context c) - { + public static void setDesiredLocale(Context c) { Locale current = Locale.getDefault(); boolean defForce = true; - if (current.getLanguage().equals(new Locale("de").getLanguage())) + + /* Languages that have proofreaders */ + String[] whitelisted = {new Locale("de").getLanguage(), new Locale("ja").getLanguage(), + new Locale("tr").getLanguage(), new Locale("zh-TW").getLanguage()}; + + String currentLanguage = current.getLanguage(); + for (String lang : whitelisted) { + if (lang.equals(currentLanguage)) { + defForce = false; + break; + } + } + + if (current.toLanguageTag().startsWith("zh-Hant")) defForce = false; boolean allow_translation = Preferences.getDefaultSharedPreferences(c).getBoolean("allow_translation", defForce); diff --git a/main/src/main/java/de/blinkt/openvpn/core/LogItem.java b/main/src/main/java/de/blinkt/openvpn/core/LogItem.java index c61cbc44..65714c43 100644 --- a/main/src/main/java/de/blinkt/openvpn/core/LogItem.java +++ b/main/src/main/java/de/blinkt/openvpn/core/LogItem.java @@ -13,11 +13,14 @@ import android.content.pm.Signature; import android.content.res.Resources; import android.os.Parcel; import android.os.Parcelable; +import android.text.TextUtils; import java.io.ByteArrayInputStream; import java.io.UnsupportedEncodingException; +import java.lang.reflect.Array; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; @@ -26,7 +29,9 @@ import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.FormatFlagsConversionMismatchException; import java.util.Locale; +import java.util.MissingFormatArgumentException; import java.util.UnknownFormatConversionException; +import java.util.Vector; import de.blinkt.openvpn.R; @@ -47,6 +52,14 @@ public class LogItem implements Parcelable { mArgs = args; } + public LogItem(VpnStatus.LogLevel level, int verblevel, String message, long eventLogTime) { + mMessage = message; + mLevel = level; + mVerbosityLevel = verblevel; + logtime = eventLogTime; + } + + public LogItem(VpnStatus.LogLevel level, int verblevel, String message) { mMessage = message; mLevel = level; @@ -84,8 +97,6 @@ public class LogItem implements Parcelable { other.mLevel.equals(mLevel)) && mVerbosityLevel == other.mVerbosityLevel && logtime == other.logtime; - - } public byte[] getMarschaledBytes() throws UnsupportedEncodingException, BufferOverflowException { @@ -195,7 +206,7 @@ public class LogItem implements Parcelable { } private void marschalString(String str, ByteBuffer bb) throws UnsupportedEncodingException { - byte[] utf8bytes = str.getBytes("UTF-8"); + byte[] utf8bytes = str.getBytes(StandardCharsets.UTF_8); bb.putInt(utf8bytes.length); bb.put(utf8bytes); } @@ -204,7 +215,7 @@ public class LogItem implements Parcelable { int len = bb.getInt(); byte[] utf8bytes = new byte[len]; bb.get(utf8bytes); - return new String(utf8bytes, "UTF-8"); + return new String(utf8bytes, StandardCharsets.UTF_8); } @@ -240,6 +251,11 @@ public class LogItem implements Parcelable { mMessage = msg; } + public LogItem(VpnStatus.LogLevel loglevel, String msg, long logEventTime) { + mLevel = loglevel; + mMessage = msg; + logtime = logEventTime; + } public LogItem(VpnStatus.LogLevel loglevel, int ressourceId) { mRessourceId = ressourceId; @@ -258,7 +274,11 @@ public class LogItem implements Parcelable { if (mArgs == null) return c.getString(mRessourceId); else - return c.getString(mRessourceId, mArgs); + try { + return c.getString(mRessourceId, mArgs); + } catch (MissingFormatArgumentException ie) { + return "ERROR MISSING ARGUMENT(" + ie.getMessage() + "): " + getString(null); + } } catch (Resources.NotFoundException re) { return getString(null); } @@ -324,10 +344,15 @@ public class LogItem implements Parcelable { CertificateFactory cf = CertificateFactory.getInstance("X.509"); X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(raw.toByteArray())); MessageDigest md = MessageDigest.getInstance("SHA-1"); + MessageDigest mdsha256 = MessageDigest.getInstance("SHA-256"); + byte[] der = cert.getEncoded(); md.update(der); byte[] digest = md.digest(); + mdsha256.update(der); + byte[] digestSha256 = mdsha256.digest(); + if (Arrays.equals(digest, VpnStatus.officalkey)) apksign = c.getString(R.string.official_build); else if (Arrays.equals(digest, VpnStatus.officaldebugkey)) @@ -336,8 +361,15 @@ public class LogItem implements Parcelable { 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()); + else if (Arrays.equals(digestSha256, VpnStatus.officialO2Key)) + apksign = c.getString(R.string.official_o2build); + else { + Vector<String> hexnums = new Vector<>(); + for (byte b: digestSha256) { + hexnums.add(String.format(Locale.US, "%02x", b)); + } + apksign = c.getString(R.string.built_by, cert.getSubjectX500Principal().getName(), TextUtils.join(":", hexnums)); + } PackageInfo packageinfo = c.getPackageManager().getPackageInfo(c.getPackageName(), 0); version = packageinfo.versionName; diff --git a/main/src/main/java/de/blinkt/openvpn/core/NativeUtils.java b/main/src/main/java/de/blinkt/openvpn/core/NativeUtils.java index 72b2b784..9bfa5e67 100644 --- a/main/src/main/java/de/blinkt/openvpn/core/NativeUtils.java +++ b/main/src/main/java/de/blinkt/openvpn/core/NativeUtils.java @@ -30,18 +30,28 @@ public class NativeUtils { public static native String getOpenVPN3GitVersion(); - static boolean rsspssloaded = false; + private static native String getOpenSSLVersionString(); + + public static String getOpenSSLVersion() { + loadOsslUtil(); + return getOpenSSLVersionString(); + } + + static boolean osslutilloaded = false; public static byte[] addRssPssPadding(int hashtype, int MSBits, int rsa_size, byte[] from) { - if (!rsspssloaded) { - rsspssloaded = true; - System.loadLibrary("rsapss"); - } - + loadOsslUtil(); return rsapss(hashtype, MSBits, rsa_size, from); } + private static void loadOsslUtil() { + if (!osslutilloaded) { + osslutilloaded = true; + System.loadLibrary("osslutil"); + } + } + private static native byte[] rsapss(int hashtype, int MSBits, int rsa_size, byte[] from); public final static int[] openSSLlengths = { diff --git a/main/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java b/main/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java index 9c8cf363..da53831b 100644 --- a/main/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java +++ b/main/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java @@ -5,11 +5,13 @@ package de.blinkt.openvpn.core; +import android.annotation.TargetApi; import android.net.IpPrefix; +import android.os.Build; import androidx.annotation.NonNull; -import java.lang.reflect.Array; +import java.lang.annotation.Target; import java.math.BigInteger; import java.net.Inet4Address; import java.net.Inet6Address; @@ -132,6 +134,7 @@ public class NetworkSpace { } + @NonNull @Override public String toString() { //String in = included ? "+" : "-"; @@ -210,6 +213,7 @@ public class NetworkSpace { } + @TargetApi(Build.VERSION_CODES.TIRAMISU) public IpPrefix getPrefix() throws UnknownHostException { if (isV4){ /* add 0x01 00 00 00 00, so that all representations are 5 byte otherwise diff --git a/main/src/main/java/de/blinkt/openvpn/core/NetworkUtils.java b/main/src/main/java/de/blinkt/openvpn/core/NetworkUtils.java index 00f45ed4..f104d3b0 100644 --- a/main/src/main/java/de/blinkt/openvpn/core/NetworkUtils.java +++ b/main/src/main/java/de/blinkt/openvpn/core/NetworkUtils.java @@ -14,14 +14,18 @@ import android.text.TextUtils; import java.net.Inet4Address; import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.Vector; +import de.blinkt.openvpn.R; + public class NetworkUtils { public static Vector<String> getLocalNetworks(Context c, boolean ipv6) { - Vector<String> nets = new Vector<>(); ConnectivityManager conn = (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE); + Vector<String> nets = new Vector<>(); Network[] networks = conn.getAllNetworks(); for (Network network : networks) { NetworkInfo ni = conn.getNetworkInfo(network); @@ -29,6 +33,10 @@ public class NetworkUtils { NetworkCapabilities nc = conn.getNetworkCapabilities(network); + // Ignore network if it has no capabilities + if (nc == null) + continue; + // Skip VPN networks like ourselves if (nc.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) continue; @@ -40,8 +48,16 @@ public class NetworkUtils { for (LinkAddress la : li.getLinkAddresses()) { if ((la.getAddress() instanceof Inet4Address && !ipv6) || - (la.getAddress() instanceof Inet6Address && ipv6)) - nets.add(la.toString()); + (la.getAddress() instanceof Inet6Address && ipv6)) { + //nets.add(la.toString()); + NetworkSpace.IpAddress ipaddress; + if (la.getAddress() instanceof Inet6Address) + ipaddress = new NetworkSpace.IpAddress((Inet6Address) la.getAddress(), la.getPrefixLength(), true); + else + ipaddress = new NetworkSpace.IpAddress(new CIDRIP(la.getAddress().getHostAddress(), la.getPrefixLength()), true); + + nets.add(ipaddress.toString()); + } } } diff --git a/main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java b/main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java index d3de35f9..0e8793e4 100644 --- a/main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java +++ b/main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java @@ -14,16 +14,18 @@ import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.UiModeManager; +import android.app.job.JobInfo; +import android.app.job.JobScheduler; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.pm.ShortcutManager; import android.content.res.Configuration; import android.content.res.Resources; import android.net.ConnectivityManager; -import android.net.IpPrefix; import android.net.ProxyInfo; import android.net.Uri; import android.net.VpnService; @@ -35,6 +37,7 @@ import android.os.HandlerThread; import android.os.IBinder; import android.os.Message; import android.os.ParcelFileDescriptor; +import android.os.PersistableBundle; import android.os.RemoteException; import android.system.OsConstants; import android.text.TextUtils; @@ -84,17 +87,27 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac private static final int PRIORITY_DEFAULT = 0; private static final int PRIORITY_MAX = 2; private static boolean mNotificationAlwaysVisible = false; - private final Vector<String> mDnslist = new Vector<>(); - private final NetworkSpace mRoutes = new NetworkSpace(); - private final NetworkSpace mRoutesv6 = new NetworkSpace(); + + static class TunConfig { + private final Vector<String> mDnslist = new Vector<>(); + private final NetworkSpace mRoutes = new NetworkSpace(); + private final NetworkSpace mRoutesv6 = new NetworkSpace(); + private String mDomain = null; + private CIDRIP mLocalIP = null; + private int mMtu; + private String mLocalIPv6 = null; + + private ProxyInfo mProxyInfo; + }; + + private TunConfig tunConfig = new TunConfig(); + private final Object mProcessLock = new Object(); private String lastChannel; private Thread mProcessThread = null; private VpnProfile mProfile; - private String mDomain = null; - private CIDRIP mLocalIP = null; - private int mMtu; - private String mLocalIPv6 = null; + + private DeviceStateReceiver mDeviceStateReceiver; private boolean mDisplayBytecount = false; private boolean mStarting = false; @@ -135,12 +148,11 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac }; - private String mLastTunCfg; + private TunConfig mLastTunCfg; private String mRemoteGW; private Handler guiHandler; private Toast mlastToast; private Runnable mOpenVPNThread; - private ProxyInfo mProxyInfo; private HandlerThread mCommandHandlerThread; private Handler mCommandHandler; @@ -180,10 +192,14 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } } + + @Override public void addAllowedExternalApp(String packagename) throws RemoteException { ExternalAppDatabase extapps = new ExternalAppDatabase(OpenVPNService.this); - extapps.addApp(packagename); + if(extapps.checkAllowingModifyingRemoteControl(this)) { + extapps.addApp(packagename); + } } @Override @@ -213,7 +229,9 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac @Override public void onRevoke() { VpnStatus.logError(R.string.permission_revoked); - mManagement.stopVPN(false); + final OpenVPNManagement managment = mManagement; + mCommandHandler.post(() -> managment.stopVPN(false)); + endVpnService(); } @@ -222,7 +240,25 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac endVpnService(); } + private boolean isAlwaysActiveEnabled() + { + SharedPreferences prefs = Preferences.getDefaultSharedPreferences(this); + return prefs.getBoolean("restartvpnonboot", false); + } + + boolean isVpnAlwaysOnEnabled() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + return isAlwaysOn(); + } + return false; + } + + private void endVpnService() { + if (!isVpnAlwaysOnEnabled() && !isAlwaysActiveEnabled()) { + /* if we should be an always on VPN, keep the timer running */ + keepVPNAlive.unscheduleKeepVPNAliveJobService(this); + } synchronized (mProcessLock) { mProcessThread = null; } @@ -519,9 +555,13 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac private VpnProfile fetchVPNProfile(Intent intent) { + String startReason; if (intent != null && intent.hasExtra(getPackageName() + ".profileUUID")) { String profileUUID = intent.getStringExtra(getPackageName() + ".profileUUID"); int profileVersion = intent.getIntExtra(getPackageName() + ".profileVersion", 0); + startReason = intent.getStringExtra(getPackageName() + ".startReason"); + if (startReason == null) + startReason = "(unknown)"; // Try for 10s to get current version of the profile mProfile = ProfileManager.get(this, profileUUID, profileVersion, 100); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { @@ -531,10 +571,13 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } else { /* The intent is null when we are set as always-on or the service has been restarted. */ mProfile = ProfileManager.getLastConnectedProfile(this); + startReason = "Using last connected profile (started with null intent, always-on or restart after crash)"; VpnStatus.logInfo(R.string.service_restarted); /* Got no profile, just stop */ if (mProfile == null) { + startReason = "could not get last connected profile, using default (started with null intent, always-on or restart after crash)"; + Log.d("OpenVPN", "Got no last connected profile on null intent. Assuming always on."); mProfile = ProfileManager.getAlwaysOnVPN(this); @@ -546,6 +589,10 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac /* Do the asynchronous keychain certificate stuff */ mProfile.checkForRestart(this); } + String name = "(null)"; + if (mProfile != null) + name = mProfile.getName(); + VpnStatus.logDebug(String.format("Fetched VPN profile (%s) triggered by %s", name, startReason)); return mProfile; } @@ -558,6 +605,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac ProfileManager.setConnectedVpnProfile(this, mProfile); VpnStatus.setConnectedVPNProfile(mProfile.getUUIDString()); + keepVPNAlive.scheduleKeepVPNAliveJobService(this, vp); String nativeLibraryDirectory = getApplicationInfo().nativeLibraryDir; String tmpDir; @@ -575,8 +623,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac // Set a flag that we are starting a new VPN mStarting = true; // Stop the previous session by interrupting the thread. - - stopOldOpenVPNProcess(); + stopOldOpenVPNProcess(mManagement, mOpenVPNThread); // An old running VPN should now be exited mStarting = false; @@ -635,11 +682,12 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } - private void stopOldOpenVPNProcess() { - if (mManagement != null) { - if (mOpenVPNThread != null) - ((OpenVPNThread) mOpenVPNThread).setReplaceConnection(); - if (mManagement.stopVPN(true)) { + private void stopOldOpenVPNProcess(OpenVPNManagement management, + Runnable mamanagmentThread) { + if (management != null) { + if (mamanagmentThread != null) + ((OpenVPNThread) mamanagmentThread).setReplaceConnection(); + if (management.stopVPN(true)) { // an old was asked to exit, wait 1s try { Thread.sleep(1000); @@ -708,27 +756,38 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac VpnStatus.flushLog(); } - private String getTunConfigString() { + private static String getTunConfigString(TunConfig tc) { // The format of the string is not important, only that // two identical configurations produce the same result + if (tc == null) + return "NULL"; + String cfg = "TUNCFG UNQIUE STRING ips:"; - if (mLocalIP != null) - cfg += mLocalIP.toString(); - if (mLocalIPv6 != null) - cfg += mLocalIPv6; + if (tc.mLocalIP != null) + cfg += tc.mLocalIP.toString(); + if (tc.mLocalIPv6 != null) + cfg += tc.mLocalIPv6; - cfg += "routes: " + TextUtils.join("|", mRoutes.getNetworks(true)) + TextUtils.join("|", mRoutesv6.getNetworks(true)); - cfg += "excl. routes:" + TextUtils.join("|", mRoutes.getNetworks(false)) + TextUtils.join("|", mRoutesv6.getNetworks(false)); - cfg += "dns: " + TextUtils.join("|", mDnslist); - cfg += "domain: " + mDomain; - cfg += "mtu: " + mMtu; - cfg += "proxyInfo: " + mProxyInfo; + cfg += "routes: " + TextUtils.join("|", tc.mRoutes.getNetworks(true)) + TextUtils.join("|", tc.mRoutesv6.getNetworks(true)); + cfg += "excl. routes:" + TextUtils.join("|", tc.mRoutes.getNetworks(false)) + TextUtils.join("|", tc.mRoutesv6.getNetworks(false)); + cfg += "dns: " + TextUtils.join("|", tc.mDnslist); + cfg += "domain: " + tc.mDomain; + cfg += "mtu: " + tc.mMtu; + cfg += "proxyInfo: " + tc.mProxyInfo; return cfg; } public ParcelFileDescriptor openTun() { + ParcelFileDescriptor pfd = openTun(tunConfig); + + // Reset information + mLastTunCfg = tunConfig; + tunConfig = new TunConfig(); + return pfd; + } + private ParcelFileDescriptor openTun(TunConfig tc) { //Debug.startMethodTracing(getExternalFilesDir(null).toString() + "/opentun.trace", 40* 1024 * 1024); @@ -747,36 +806,36 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac allowAllAFFamilies(builder); } - if (mLocalIP == null && mLocalIPv6 == null) { + if (tc.mLocalIP == null && tc.mLocalIPv6 == null) { VpnStatus.logError(getString(R.string.opentun_no_ipaddr)); return null; } - if (mLocalIP != null) { + if (tc.mLocalIP != null) { // OpenVPN3 manages excluded local networks by callback if (!VpnProfile.doUseOpenVPN3(this)) - addLocalNetworksToRoutes(); + addLocalNetworksToRoutes(tc); try { - builder.addAddress(mLocalIP.mIp, mLocalIP.len); + builder.addAddress(tc.mLocalIP.mIp, tc.mLocalIP.len); } catch (IllegalArgumentException iae) { - VpnStatus.logError(R.string.dns_add_error, mLocalIP, iae.getLocalizedMessage()); + VpnStatus.logError(R.string.dns_add_error, tc.mLocalIP, iae.getLocalizedMessage()); return null; } } - if (mLocalIPv6 != null) { - String[] ipv6parts = mLocalIPv6.split("/"); + if (tc.mLocalIPv6 != null) { + String[] ipv6parts = tc.mLocalIPv6.split("/"); try { builder.addAddress(ipv6parts[0], Integer.parseInt(ipv6parts[1])); } catch (IllegalArgumentException iae) { - VpnStatus.logError(R.string.ip_add_error, mLocalIPv6, iae.getLocalizedMessage()); + VpnStatus.logError(R.string.ip_add_error, tc.mLocalIPv6, iae.getLocalizedMessage()); return null; } } - for (String dns : mDnslist) { + for (String dns : tc.mDnslist) { try { builder.addDnsServer(dns); } catch (IllegalArgumentException iae) { @@ -785,15 +844,15 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } String release = Build.VERSION.RELEASE; - builder.setMtu(mMtu); + builder.setMtu(tc.mMtu); - Collection<IpAddress> positiveIPv4Routes = mRoutes.getPositiveIPList(); - Collection<IpAddress> positiveIPv6Routes = mRoutesv6.getPositiveIPList(); + Collection<IpAddress> positiveIPv4Routes = tc.mRoutes.getPositiveIPList(); + Collection<IpAddress> positiveIPv6Routes = tc.mRoutesv6.getPositiveIPList(); - if ("samsung".equals(Build.BRAND) && mDnslist.size() >= 1) { + if ("samsung".equals(Build.BRAND) && tc.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); + IpAddress dnsServer = new IpAddress(new CIDRIP(tc.mDnslist.get(0), 32), true); boolean dnsIncluded = false; for (IpAddress net : positiveIPv4Routes) { if (net.containsNet(dnsServer)) { @@ -801,28 +860,28 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } } 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)); + 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.", tc.mDnslist.get(0)); VpnStatus.logWarning(samsungwarning); positiveIPv4Routes.add(dnsServer); } } catch (Exception e) { // If it looks like IPv6 ignore error - if (!mDnslist.get(0).contains(":")) - VpnStatus.logError("Error parsing DNS Server IP: " + mDnslist.get(0)); + if (!tc.mDnslist.get(0).contains(":")) + VpnStatus.logError("Error parsing DNS Server IP: " + tc.mDnslist.get(0)); } } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - installRoutesExcluded(builder, mRoutes); - installRoutesExcluded(builder, mRoutesv6); + installRoutesExcluded(builder, tc.mRoutes); + installRoutesExcluded(builder, tc.mRoutesv6); } else { installRoutesPostiveOnly(builder, positiveIPv4Routes, positiveIPv6Routes); } - if (mDomain != null) - builder.addSearchDomain(mDomain); + if (tc.mDomain != null) + builder.addSearchDomain(tc.mDomain); String ipv4info; String ipv6info; @@ -835,32 +894,34 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } int ipv4len; - if (mLocalIP != null) { - ipv4len = mLocalIP.len; - ipv4info = mLocalIP.mIp; + if (tc.mLocalIP != null) { + ipv4len = tc.mLocalIP.len; + ipv4info = tc.mLocalIP.mIp; } else { ipv4len = -1; } - if (mLocalIPv6 != null) { - ipv6info = mLocalIPv6; + if (tc.mLocalIPv6 != null) { + ipv6info = tc.mLocalIPv6; } - if ((!mRoutes.getNetworks(false).isEmpty() || !mRoutesv6.getNetworks(false).isEmpty()) && isLockdownEnabledCompat()) { + if ((!tc.mRoutes.getNetworks(false).isEmpty() || !tc.mRoutesv6.getNetworks(false).isEmpty()) && isLockdownEnabledCompat()) { VpnStatus.logInfo("VPN lockdown enabled (do not allow apps to bypass VPN) enabled. Route exclusion will not allow apps to bypass VPN (e.g. bypass VPN for local networks)"); } - VpnStatus.logInfo(R.string.local_ip_info, ipv4info, ipv4len, ipv6info, mMtu); - VpnStatus.logInfo(R.string.dns_server_info, TextUtils.join(", ", mDnslist), mDomain); - VpnStatus.logInfo(R.string.routes_info_incl, TextUtils.join(", ", mRoutes.getNetworks(true)), TextUtils.join(", ", mRoutesv6.getNetworks(true))); - VpnStatus.logInfo(R.string.routes_info_excl, TextUtils.join(", ", mRoutes.getNetworks(false)), TextUtils.join(", ", mRoutesv6.getNetworks(false))); - if (mProxyInfo != null) { - VpnStatus.logInfo(R.string.proxy_info, mProxyInfo.getHost(), mProxyInfo.getPort()); + VpnStatus.logInfo(R.string.local_ip_info, ipv4info, ipv4len, ipv6info, tc.mMtu); + VpnStatus.logInfo(R.string.dns_server_info, TextUtils.join(", ", tc.mDnslist), tc.mDomain); + VpnStatus.logInfo(R.string.routes_info_incl, TextUtils.join(", ", tc.mRoutes.getNetworks(true)), TextUtils.join(", ", tc.mRoutesv6.getNetworks(true))); + VpnStatus.logInfo(R.string.routes_info_excl, TextUtils.join(", ", tc.mRoutes.getNetworks(false)), TextUtils.join(", ", tc.mRoutesv6.getNetworks(false))); + if (tc.mProxyInfo != null) { + VpnStatus.logInfo(R.string.proxy_info, tc.mProxyInfo.getHost(), tc.mProxyInfo.getPort()); } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { /* On Tiramisu we install the routes exactly like promised */ VpnStatus.logDebug(R.string.routes_debug, TextUtils.join(", ", positiveIPv4Routes), TextUtils.join(", ", positiveIPv6Routes)); } + //VpnStatus.logInfo(String.format("Always active %s", isAlwaysOn() ? "on" : "off")); + setAllowedVpnPackages(builder); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { // VPN always uses the default network @@ -874,31 +935,20 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } String session = mProfile.mName; - if (mLocalIP != null && mLocalIPv6 != null) - session = getString(R.string.session_ipv6string, session, mLocalIP, mLocalIPv6); - else if (mLocalIP != null) - session = getString(R.string.session_ipv4string, session, mLocalIP); + if (tc.mLocalIP != null && tc.mLocalIPv6 != null) + session = getString(R.string.session_ipv6string, session, tc.mLocalIP, tc.mLocalIPv6); + else if (tc.mLocalIP != null) + session = getString(R.string.session_ipv4string, session, tc.mLocalIP); else - session = getString(R.string.session_ipv4string, session, mLocalIPv6); + session = getString(R.string.session_ipv4string, session, tc.mLocalIPv6); builder.setSession(session); // No DNS Server, log a warning - if (mDnslist.size() == 0) + if (tc.mDnslist.size() == 0) VpnStatus.logInfo(R.string.warn_no_dns); - setHttpProxy(builder); - - mLastTunCfg = getTunConfigString(); - - // Reset information - mDnslist.clear(); - mRoutes.clear(); - mRoutesv6.clear(); - mLocalIP = null; - mLocalIPv6 = null; - mDomain = null; - mProxyInfo = null; + setHttpProxy(builder, tc); builder.setConfigureIntent(getGraphPendingIntent()); @@ -959,10 +1009,10 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } } - private void setHttpProxy(Builder builder) { - if (mProxyInfo != null && Build.VERSION.SDK_INT >= 29) { - builder.setHttpProxy(mProxyInfo); - } else if (mProxyInfo != null) { + private void setHttpProxy(Builder builder, TunConfig tc) { + if (tc.mProxyInfo != null && Build.VERSION.SDK_INT >= 29) { + builder.setHttpProxy(tc.mProxyInfo); + } else if (tc.mProxyInfo != null) { VpnStatus.logWarning("HTTP Proxy needs Android 10 or later."); } } @@ -982,27 +1032,24 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac builder.allowFamily(OsConstants.AF_INET6); } - private void addLocalNetworksToRoutes() { + private void addLocalNetworksToRoutes(TunConfig tc) { for (String net : NetworkUtils.getLocalNetworks(this, false)) { String[] netparts = net.split("/"); String ipAddr = netparts[0]; int netMask = Integer.parseInt(netparts[1]); - if (ipAddr.equals(mLocalIP.mIp)) + if (ipAddr.equals(tc.mLocalIP.mIp)) continue; if(mProfile.mAllowLocalLAN) - mRoutes.addIP(new CIDRIP(ipAddr, netMask), false); + tc.mRoutes.addIP(new CIDRIP(ipAddr, netMask), false); } - // IPv6 is Lollipop+ only so we can skip the lower than KITKAT case if (mProfile.mAllowLocalLAN) { for (String net : NetworkUtils.getLocalNetworks(this, true)) { addRoutev6(net, false); ; } } - - } private void setAllowedVpnPackages(Builder builder) { @@ -1065,12 +1112,12 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } public void addDNS(String dns) { - mDnslist.add(dns); + tunConfig.mDnslist.add(dns); } public void setDomain(String domain) { - if (mDomain == null) { - mDomain = domain; + if (tunConfig.mDomain == null) { + tunConfig.mDomain = domain; } } @@ -1078,12 +1125,12 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac * Route that is always included, used by the v3 core */ public void addRoute(CIDRIP route, boolean include) { - mRoutes.addIP(route, include); + tunConfig.mRoutes.addIP(route, include); } public boolean addHttpProxy(String proxy, int port) { try { - mProxyInfo = ProxyInfo.buildDirectProxy(proxy, port); + tunConfig.mProxyInfo = ProxyInfo.buildDirectProxy(proxy, port); } catch (Exception e) { VpnStatus.logError("Could not set proxy" + e.getLocalizedMessage()); return false; @@ -1097,11 +1144,11 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac IpAddress gatewayIP = new IpAddress(new CIDRIP(gateway, 32), false); - if (mLocalIP == null) { + if (tunConfig.mLocalIP == null) { VpnStatus.logError("Local IP address unset and received. Neither pushed server config nor local config specifies an IP addresses. Opening tun device is most likely going to fail."); return; } - IpAddress localNet = new IpAddress(mLocalIP, true); + IpAddress localNet = new IpAddress(tunConfig.mLocalIP, true); if (localNet.containsNet(gatewayIP)) include = true; @@ -1117,7 +1164,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac if (route.normalise()) VpnStatus.logWarning(R.string.route_not_netip, dest, route.len, route.mIp); - mRoutes.addIP(route, include); + tunConfig.mRoutes.addIP(route, include); } public void addRoutev6(String network, String device) { @@ -1132,7 +1179,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac try { Inet6Address ip = (Inet6Address) InetAddress.getAllByName(v6parts[0])[0]; int mask = Integer.parseInt(v6parts[1]); - mRoutesv6.addIPv6(ip, mask, included); + tunConfig.mRoutesv6.addIPv6(ip, mask, included); } catch (UnknownHostException e) { VpnStatus.logException(e); @@ -1147,21 +1194,21 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } public void setMtu(int mtu) { - mMtu = mtu; + tunConfig.mMtu = mtu; } public void setLocalIP(CIDRIP cdrip) { - mLocalIP = cdrip; + tunConfig.mLocalIP = cdrip; } public void setLocalIP(String local, String netmask, int mtu, String mode) { - mLocalIP = new CIDRIP(local, netmask); - mMtu = mtu; + tunConfig.mLocalIP = new CIDRIP(local, netmask); + tunConfig.mMtu = mtu; mRemoteGW = null; long netMaskAsInt = CIDRIP.getInt(netmask); - if (mLocalIP.len == 32 && !netmask.equals("255.255.255.255")) { + if (tunConfig.mLocalIP.len == 32 && !netmask.equals("255.255.255.255")) { // get the netmask as IP int masklen; @@ -1175,22 +1222,22 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } // Netmask is Ip address +/-1, assume net30/p2p with small net - if ((netMaskAsInt & mask) == (mLocalIP.getInt() & mask)) { - mLocalIP.len = masklen; + if ((netMaskAsInt & mask) == (tunConfig.mLocalIP.getInt() & mask)) { + tunConfig.mLocalIP.len = masklen; } else { - mLocalIP.len = 32; + tunConfig.mLocalIP.len = 32; if (!"p2p".equals(mode)) VpnStatus.logWarning(R.string.ip_not_cidr, local, netmask, mode); } } - if (("p2p".equals(mode) && mLocalIP.len < 32) || ("net30".equals(mode) && mLocalIP.len < 30)) { + if (("p2p".equals(mode) && tunConfig.mLocalIP.len < 32) || ("net30".equals(mode) && tunConfig.mLocalIP.len < 30)) { VpnStatus.logWarning(R.string.ip_looks_like_subnet, local, netmask, mode); } /* Workaround for Lollipop and higher, it does not route traffic to the VPNs own network mask */ - if (mLocalIP.len <= 31) { - CIDRIP interfaceRoute = new CIDRIP(mLocalIP.mIp, mLocalIP.len); + if (tunConfig.mLocalIP.len <= 31) { + CIDRIP interfaceRoute = new CIDRIP(tunConfig.mLocalIP.mIp, tunConfig.mLocalIP.len); interfaceRoute.normalise(); addRoute(interfaceRoute, true); } @@ -1201,7 +1248,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } public void setLocalIPv6(String ipv6addr) { - mLocalIPv6 = ipv6addr; + tunConfig.mLocalIPv6 = ipv6addr; } @Override @@ -1279,8 +1326,8 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } public String getTunReopenStatus() { - String currentConfiguration = getTunConfigString(); - if (currentConfiguration.equals(mLastTunCfg)) { + String currentConfiguration = getTunConfigString(tunConfig); + if (currentConfiguration.equals(getTunConfigString(mLastTunCfg))) { return "NOACTION"; } else { return "OPEN_BEFORE_CLOSE"; diff --git a/main/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java b/main/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java index 51fc58eb..37eb34dd 100644 --- a/main/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java +++ b/main/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java @@ -15,6 +15,7 @@ import java.io.IOException; import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
@@ -135,7 +136,7 @@ public class OpenVPNThread implements Runnable { InputStream in = mProcess.getInputStream();
OutputStream out = mProcess.getOutputStream();
- BufferedReader br = new BufferedReader(new InputStreamReader(in));
+ BufferedReader br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
mOutputStream = out;
mStreamFuture.run();
diff --git a/main/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java b/main/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java index 11b2608b..8edae328 100644 --- a/main/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java +++ b/main/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java @@ -47,12 +47,12 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { private pauseReason lastPauseReason = pauseReason.noNetwork;
private PausedStateCallback mPauseCallback;
private boolean mShuttingDown;
- private Runnable mResumeHoldRunnable = () -> {
+ private final Runnable mResumeHoldRunnable = () -> {
if (shouldBeRunning()) {
releaseHoldCmd();
}
};
- private Runnable orbotStatusTimeOutRunnable = new Runnable() {
+ private final Runnable orbotStatusTimeOutRunnable = new Runnable() {
@Override
public void run() {
sendProxyCMD(Connection.ProxyType.SOCKS5, "127.0.0.1", Integer.toString(OrbotHelper.SOCKS_PROXY_PORT_DEFAULT), false);
@@ -148,8 +148,6 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { VpnStatus.logException(e);
}
return false;
-
-
}
/**
@@ -402,7 +400,7 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { if (waittime > 1)
VpnStatus.updateStateString("CONNECTRETRY", String.valueOf(waittime),
R.string.state_waitconnectretry, ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET);
- mResumeHandler.postDelayed(mResumeHoldRunnable, waittime * 1000);
+ mResumeHandler.postDelayed(mResumeHoldRunnable, waittime * 1000L);
if (waittime > 5)
VpnStatus.logInfo(R.string.state_waitconnectretry, String.valueOf(waittime));
else
@@ -618,14 +616,8 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { managmentCommand(cmd);
}
- private boolean sendTunFD(String needed, String extra) {
- if (!extra.equals("tun")) {
- // We only support tun
- VpnStatus.logError(String.format("Device type %s requested, but only tun is possible with the Android API, sorry!", extra));
- return false;
- }
- ParcelFileDescriptor pfd = mOpenVPNService.openTun();
+ private boolean sendCommandWithFd(String cmd, ParcelFileDescriptor pfd) {
if (pfd == null)
return false;
@@ -638,26 +630,38 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { setInt.invoke(fdtosend, fdint);
FileDescriptor[] fds = {fdtosend};
- mSocket.setFileDescriptorsForSend(fds);
// Trigger a send so we can close the fd on our side of the channel
// The API documentation fails to mention that it will not reset the file descriptor to
// be send and will happily send the file descriptor on every write ...
- String cmd = String.format("needok '%s' %s\n", needed, "ok");
+ mSocket.setFileDescriptorsForSend(fds);
+
managmentCommand(cmd);
// Set the FileDescriptor to null to stop this mad behavior
mSocket.setFileDescriptorsForSend(null);
-
pfd.close();
- return true;
- } catch (NoSuchMethodException | IllegalArgumentException | InvocationTargetException |
- IOException | IllegalAccessException exp) {
+
+ } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException |
+ IOException exp) {
VpnStatus.logException("Could not send fd over socket", exp);
+ return false;
}
+ return true;
+ }
- return false;
+ private boolean sendTunFD(String needed, String extra) {
+ if (!extra.equals("tun")) {
+ // We only support tun
+ VpnStatus.logError(String.format("Device type %s requested, but only tun is possible with the Android API, sorry!", extra));
+
+ return false;
+ }
+ ParcelFileDescriptor pfd = mOpenVPNService.openTun();
+
+ String cmd = String.format("needok '%s' %s\n", needed, "ok");
+ return sendCommandWithFd(cmd, pfd);
}
private void processPWCommand(String argument) {
diff --git a/main/src/main/java/de/blinkt/openvpn/core/ProfileManager.java b/main/src/main/java/de/blinkt/openvpn/core/ProfileManager.java index b46a8f10..9d59e26b 100644 --- a/main/src/main/java/de/blinkt/openvpn/core/ProfileManager.java +++ b/main/src/main/java/de/blinkt/openvpn/core/ProfileManager.java @@ -51,10 +51,10 @@ public class ProfileManager { return instance.profiles.get(key); } - private static void checkInstance(Context context) { + private synchronized static void checkInstance(Context context) { if (instance == null) { instance = new ProfileManager(); - ProfileEncryption.initMasterCryptAlias(); + ProfileEncryption.initMasterCryptAlias(context); instance.loadVPNList(context); } } @@ -146,7 +146,7 @@ public class ProfileManager { if (encryptedFileOld.exists()) { encryptedFileOld.delete(); } - } catch (IOException ioe) + } catch (IOException | GeneralSecurityException ioe) { VpnStatus.logException(VpnStatus.LogLevel.INFO, "Error trying to write an encrypted VPN profile, disabling " + "encryption", ioe); @@ -174,7 +174,7 @@ public class ProfileManager { } - } catch (IOException | GeneralSecurityException e) { + } catch (IOException e) { VpnStatus.logException("saving VPN profile", e); throw new RuntimeException(e); } @@ -250,7 +250,7 @@ public class ProfileManager { editor.apply(); } - public void addProfile(VpnProfile profile) { + public synchronized void addProfile(VpnProfile profile) { profiles.put(profile.getUUID().toString(), profile); } @@ -259,7 +259,7 @@ public class ProfileManager { * profiles * @param context */ - public void refreshVPNList(Context context) + public synchronized void refreshVPNList(Context context) { SharedPreferences listpref = Preferences.getSharedPreferencesMulti(PREFS_NAME, context); Set<String> vlist = listpref.getStringSet("vpnlist", null); @@ -283,7 +283,7 @@ public class ProfileManager { } } - private void loadVPNList(Context context) { + private synchronized void loadVPNList(Context context) { profiles = new HashMap<>(); SharedPreferences listpref = Preferences.getSharedPreferencesMulti(PREFS_NAME, context); Set<String> vlist = listpref.getStringSet("vpnlist", null); @@ -298,7 +298,7 @@ public class ProfileManager { } } - private void loadVpnEntry(Context context, String vpnentry) { + private synchronized void loadVpnEntry(Context context, String vpnentry) { ObjectInputStream vpnfile = null; try { FileInputStream vpInput; @@ -339,7 +339,7 @@ public class ProfileManager { } } - public void removeProfile(Context context, VpnProfile profile) { + public synchronized void removeProfile(Context context, VpnProfile profile) { String vpnentry = profile.getUUID().toString(); profiles.remove(vpnentry); saveProfileList(context); diff --git a/main/src/main/java/de/blinkt/openvpn/core/StatusListener.java b/main/src/main/java/de/blinkt/openvpn/core/StatusListener.java index 13a88974..293a6fd4 100644 --- a/main/src/main/java/de/blinkt/openvpn/core/StatusListener.java +++ b/main/src/main/java/de/blinkt/openvpn/core/StatusListener.java @@ -5,29 +5,33 @@ package de.blinkt.openvpn.core; -import android.app.PendingIntent; +import android.app.ActivityManager; +import android.app.ApplicationExitInfo; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.os.Build; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.util.Log; + +import androidx.annotation.RequiresApi; + import de.blinkt.openvpn.BuildConfig; import de.blinkt.openvpn.core.VpnStatus.LogLevel; import java.io.DataInputStream; import java.io.File; import java.io.IOException; +import java.util.List; /** * Created by arne on 09.11.16. */ public class StatusListener implements VpnStatus.LogListener { - private File mCacheDir; - private Context mContext; private final IStatusCallbacks mCallback = new IStatusCallbacks.Stub() { @Override public void newLogItem(LogItem item) throws RemoteException { @@ -37,7 +41,19 @@ public class StatusListener implements VpnStatus.LogListener { @Override public void updateStateString(String state, String msg, int resid, ConnectionStatus level, Intent intent) throws RemoteException { - VpnStatus.updateStateString(state, msg, resid, level, intent); + Intent newIntent = reCreateIntent(intent); + VpnStatus.updateStateString(state, msg, resid, level, newIntent); + } + + private Intent reCreateIntent(Intent intent) { + /* To avoid UnsafeIntentLaunchViolation we recreate the intent that we passed + * to ourselves via the AIDL interface */ + if (intent == null) + return null; + Intent newIntent = new Intent(intent.getAction(), intent.getData()); + if (intent.getExtras() != null) + newIntent.putExtras(intent.getExtras()); + return newIntent; } @Override @@ -50,6 +66,7 @@ public class StatusListener implements VpnStatus.LogListener { VpnStatus.setConnectedVPNProfile(uuid); } }; + private File mCacheDir; private final ServiceConnection mConnection = new ServiceConnection() { @@ -102,6 +119,7 @@ public class StatusListener implements VpnStatus.LogListener { } }; + private Context mContext; void init(Context c) { @@ -112,6 +130,35 @@ public class StatusListener implements VpnStatus.LogListener { c.bindService(intent, mConnection, Context.BIND_AUTO_CREATE); this.mContext = c; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) + logLatestExitReasons(c); + } + + @RequiresApi(Build.VERSION_CODES.R) + private void logLatestExitReasons(Context c) { + ActivityManager activityManager = (ActivityManager) c.getSystemService(Context.ACTIVITY_SERVICE); + List<ApplicationExitInfo> exitReasons = activityManager.getHistoricalProcessExitReasons(null, 0, 5); + ApplicationExitInfo lastguiexit = null; + ApplicationExitInfo lastserviceexit = null; + for (ApplicationExitInfo aei : exitReasons) { + if (aei.getProcessName().endsWith(":openvpn")) { + if (lastserviceexit == null || aei.getTimestamp() > lastserviceexit.getTimestamp()) + lastserviceexit = aei; + } else { + if (lastguiexit == null || aei.getTimestamp() > lastguiexit.getTimestamp()) + lastguiexit = aei; + } + } + logExitNotification(lastserviceexit, "Last exit reason reported by Android for Service Process: "); + logExitNotification(lastguiexit, "Last exit reason reported by Android for UI Process: "); + + } + + private void logExitNotification(ApplicationExitInfo aei, String s) { + if (aei != null) { + LogItem li = new LogItem(LogLevel.DEBUG, s + aei, aei.getTimestamp()); + VpnStatus.newLogItemIfUnique(li); + } } @Override diff --git a/main/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java b/main/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java index c859e845..bc04bc5e 100644 --- a/main/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java +++ b/main/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java @@ -100,8 +100,8 @@ public class VPNLaunchHelper { } - public static void startOpenVpn(VpnProfile startprofile, Context context) { - Intent startVPN = startprofile.prepareStartService(context); + public static void startOpenVpn(VpnProfile startprofile, Context context, String startReason) { + Intent startVPN = startprofile.getStartServiceIntent(context, startReason); if (startVPN != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) //noinspection NewApi diff --git a/main/src/main/java/de/blinkt/openvpn/core/VpnStatus.java b/main/src/main/java/de/blinkt/openvpn/core/VpnStatus.java index c8e69414..d1814fc2 100644 --- a/main/src/main/java/de/blinkt/openvpn/core/VpnStatus.java +++ b/main/src/main/java/de/blinkt/openvpn/core/VpnStatus.java @@ -15,14 +15,13 @@ import java.io.File; import java.io.PrintWriter; import java.io.StringWriter; import java.util.LinkedList; +import java.util.ListIterator; import java.util.Locale; import java.util.Vector; import de.blinkt.openvpn.R; public class VpnStatus { - - private static final LinkedList<LogItem> logbuffer; private static Vector<LogListener> logListener; @@ -150,7 +149,6 @@ public class VpnStatus { VpnStatus.trafficHistory = trafficHistory; } - public enum LogLevel { INFO(2), ERROR(-2), @@ -192,6 +190,7 @@ public class VpnStatus { static final byte[] officaldebugkey = {-99, -69, 45, 71, 114, -116, 82, 66, -99, -122, 50, -70, -56, -111, 98, -35, -65, 105, 82, 43}; static final byte[] amazonkey = {-116, -115, -118, -89, -116, -112, 120, 55, 79, -8, -119, -23, 106, -114, -85, -56, -4, 105, 26, -57}; static final byte[] fdroidkey = {-92, 111, -42, -46, 123, -96, -60, 79, -27, -31, 49, 103, 11, -54, -68, -27, 17, 2, 121, 104}; + static final byte[] officialO2Key = {-50, -119, -11, 121, 121, 122, -115, 84, 90, -122, 27, -117, -14, 60, 54, 127, 41, -45, 27, 55, -14, 90, 31, 72, -26, -85, -85, 67, 35, 54, 100, 42}; private static ConnectionStatus mLastLevel = ConnectionStatus.LEVEL_NOTCONNECTED; @@ -240,7 +239,7 @@ public class VpnStatus { String nativeAPI; try { nativeAPI = NativeUtils.getNativeAPI(); - } catch (UnsatisfiedLinkError ignore) { + } catch (UnsatisfiedLinkError|NoClassDefFoundError ignore) { nativeAPI = "error"; } @@ -418,15 +417,23 @@ public class VpnStatus { } static void newLogItem(LogItem logItem) { - newLogItem(logItem, false); + newLogItem(logItem, false, false); + } + + public static void newLogItemIfUnique(LogItem li) { + newLogItem(li, false, true); } + public static void newLogItem(LogItem logItem, boolean cachedLine) + { + newLogItem(logItem, cachedLine, false); + } - synchronized static void newLogItem(LogItem logItem, boolean cachedLine) { + synchronized static void newLogItem(LogItem logItem, boolean cachedLine, boolean enforceUnique) { if (cachedLine) { logbuffer.addFirst(logItem); } else { - logbuffer.addLast(logItem); + insertLogItemByLogTime(logItem, enforceUnique); if (mLogFileHandler != null) { Message m = mLogFileHandler.obtainMessage(LogFileHandler.LOG_MESSAGE, logItem); mLogFileHandler.sendMessage(m); @@ -445,6 +452,34 @@ public class VpnStatus { } } + private static void insertLogItemByLogTime(LogItem logItem, boolean enforceUnique) { + /* Shortcut for the shortcut that it should be added at the + * end to avoid traversing the list + */ + if (!logbuffer.isEmpty() && logbuffer.getLast().getLogtime() <= logItem.getLogtime()) + { + logbuffer.addLast(logItem); + return; + } + + ListIterator<LogItem> itr = logbuffer.listIterator(); + long newItemLogTime = logItem.getLogtime(); + while(itr.hasNext()) { + LogItem laterLogItem = itr.next(); + if (enforceUnique && laterLogItem.equals(logItem)) + /* Identical object found, ignore new item */ + return; + + if (laterLogItem.getLogtime() > newItemLogTime) { + itr.previous(); + itr.add(logItem); + return; + } + } + /* no hasNext, add at the end */ + itr.add(logItem); + } + public static void logError(String msg) { newLogItem(new LogItem(LogLevel.ERROR, msg)); diff --git a/main/src/main/java/de/blinkt/openvpn/core/X509Utils.java b/main/src/main/java/de/blinkt/openvpn/core/X509Utils.java index eeb54675..e5ca561e 100644 --- a/main/src/main/java/de/blinkt/openvpn/core/X509Utils.java +++ b/main/src/main/java/de/blinkt/openvpn/core/X509Utils.java @@ -80,7 +80,7 @@ public class X509Utils { try { X509Certificate cert = (X509Certificate) getCertificatesFromFile(filename)[0]; String friendlycn = getCertificateFriendlyName(cert); - friendlycn = getCertificateValidityString(cert, c.getResources()) + friendlycn; + friendlycn = getCertificateValidityString(cert, c.getResources()) + ", " + friendlycn; return friendlycn; } catch (Exception e) { @@ -146,9 +146,9 @@ public class X509Utils { friendlyName= (String) toString.invoke(subjectName,true,defaultSymbols); - } catch (ClassNotFoundException | IllegalAccessException | NoSuchFieldException | NoSuchMethodException e) { + } catch (ClassNotFoundException | IllegalAccessException | NoSuchFieldException e) { exp =e ; - } catch (InvocationTargetException e) { + } catch (InvocationTargetException | NoSuchMethodException e) { /* Ignore this. Modern Android versions do not expose this */ exp = null; } diff --git a/main/src/main/java/de/blinkt/openvpn/core/keepVPNAlive.java b/main/src/main/java/de/blinkt/openvpn/core/keepVPNAlive.java new file mode 100644 index 00000000..b4264aba --- /dev/null +++ b/main/src/main/java/de/blinkt/openvpn/core/keepVPNAlive.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2012-2023 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.app.job.JobInfo; +import android.app.job.JobParameters; +import android.app.job.JobScheduler; +import android.app.job.JobService; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.net.VpnService; +import android.os.Build; +import android.os.PersistableBundle; + +import de.blinkt.openvpn.LaunchVPN; +import de.blinkt.openvpn.VpnProfile; + +/** + * This is a task that is run periodically to restart the VPN if tit has died for + * some reason in the background + */ +public class keepVPNAlive extends JobService implements VpnStatus.StateListener { + private ConnectionStatus mLevel = ConnectionStatus.UNKNOWN_LEVEL; + private static final int JOBID_KEEPVPNALIVE = 6231; + + @Override + public void onCreate() { + super.onCreate(); + VpnStatus.addStateListener(this); + } + + @Override + public void onDestroy() { + super.onDestroy(); + VpnStatus.removeStateListener(this); + } + + @Override + public boolean onStartJob(JobParameters jobParameters) { + if (mLevel == ConnectionStatus.UNKNOWN_LEVEL || mLevel == ConnectionStatus.LEVEL_NOTCONNECTED) { + String vpnUUID = jobParameters.getExtras().getString(LaunchVPN.EXTRA_KEY); + VpnProfile vp = ProfileManager.get(this, vpnUUID); + if (vp == null) { + VpnStatus.logError("Keepalive service cannot find VPN"); + unscheduleKeepVPNAliveJobService(this); + return false; + } + VPNLaunchHelper.startOpenVpn(vp, getApplicationContext(), "VPN keep alive Job"); + } else { + VpnStatus.logDebug("Keepalive service called but VPN still connected."); + } + + /* The job has finished */ + return false; + } + + @Override + public boolean onStopJob(JobParameters jobParameters) { + /* not doing anything */ + return true; + } + + @Override + public void updateState(String state, String logmessage, + int localizedResId, ConnectionStatus level, Intent Intent) { + mLevel = level; + } + + @Override + public void setConnectedVPN(String uuid) { + + } + + public static void scheduleKeepVPNAliveJobService(Context c, VpnProfile vp) { + ComponentName keepVPNAliveComponent = new ComponentName(c, keepVPNAlive.class); + JobInfo.Builder jib = new JobInfo.Builder(JOBID_KEEPVPNALIVE, keepVPNAliveComponent); + + /* set the VPN that should be restarted if we get killed */ + PersistableBundle extraBundle = new PersistableBundle(); + extraBundle.putString(de.blinkt.openvpn.LaunchVPN.EXTRA_KEY, vp.getUUIDString()); + jib.setExtras(extraBundle); + + /* periodic timing */ + /* The current limits are 15 minutes and 5 minutes for flex and periodic timer + * but we use a minimum of 5 minutes and 2 minutes to avoid problems if there is some + * strange Android build that allows lower lmits. + */ + long initervalMillis = Math.max(getMinPeriodMillis(), 5 * 60 * 1000L); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + long flexMillis = Math.max(JobInfo.getMinFlexMillis(), 2 * 60 * 1000L); + jib.setPeriodic(initervalMillis, flexMillis); + } + else + { + jib.setPeriodic(initervalMillis); + } + jib.setPersisted(true); + + JobScheduler jobScheduler = null; + jobScheduler = getJobScheduler(c); + + jobScheduler.schedule(jib.build()); + VpnStatus.logDebug("Scheduling VPN keep alive for VPN " + vp.mName); + } + + private static JobScheduler getJobScheduler(Context c) { + JobScheduler jobScheduler; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + jobScheduler = c.getSystemService(JobScheduler.class); + + } else { + jobScheduler = (JobScheduler) c.getSystemService(JOB_SCHEDULER_SERVICE); + } + return jobScheduler; + } + + private static long getMinPeriodMillis() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return JobInfo.getMinPeriodMillis(); + } else { + return 15 * 60 * 1000L; // 15 minutes + } + } + + public static void unscheduleKeepVPNAliveJobService(Context c) { + JobScheduler jobScheduler = getJobScheduler(c); + jobScheduler.cancel(JOBID_KEEPVPNALIVE); + VpnStatus.logDebug("Unscheduling VPN keep alive"); + } +} |