summaryrefslogtreecommitdiff
path: root/app/src/main/java/de
diff options
context:
space:
mode:
authorcyBerta <cyberta@riseup.net>2017-09-21 01:28:24 +0200
committercyBerta <cyberta@riseup.net>2017-09-21 01:28:24 +0200
commitd77b9aefea75491b50f28a6880906ba9496979f2 (patch)
tree669cd96c72836c66caee662b4224e35df205d8a0 /app/src/main/java/de
parent63a604651b9fdc24a0e18c7b95a00e9fe6bb157d (diff)
update ics-openvpn: update classes, manifest, resources and build script
Diffstat (limited to 'app/src/main/java/de')
-rw-r--r--app/src/main/java/de/blinkt/openvpn/LaunchVPN.java75
-rw-r--r--app/src/main/java/de/blinkt/openvpn/VpnProfile.java150
-rw-r--r--app/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java52
-rw-r--r--app/src/main/java/de/blinkt/openvpn/api/APIVpnProfile.java60
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/CIDRIP.java4
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java89
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/Connection.java23
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/ConnectionStatus.java47
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java17
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java61
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/LogFileHandler.java181
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/LogItem.java6
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java94
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java2
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java305
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/OpenVPNStatusService.java232
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java32
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java72
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/PasswordCache.java61
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/Preferences.java31
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/ProfileManager.java114
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/StatusListener.java109
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/TrafficHistory.java243
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java47
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java328
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/X509Utils.java9
-rw-r--r--app/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java92
27 files changed, 1835 insertions, 701 deletions
diff --git a/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java b/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java
index 16f986ae..0c3f20fb 100644
--- a/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java
+++ b/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java
@@ -7,15 +7,25 @@ package de.blinkt.openvpn;
import se.leap.bitmaskclient.R;
+import se.leap.bitmaskclient.R;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
+import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.net.VpnService;
+import android.os.Build;
import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.text.InputType;
import android.text.TextUtils;
@@ -28,10 +38,14 @@ import android.widget.EditText;
import java.io.IOException;
import de.blinkt.openvpn.activities.LogWindow;
+import de.blinkt.openvpn.core.ConnectionStatus;
+import de.blinkt.openvpn.core.IServiceStatus;
+import de.blinkt.openvpn.core.OpenVPNStatusService;
+import de.blinkt.openvpn.core.PasswordCache;
+import de.blinkt.openvpn.core.Preferences;
import de.blinkt.openvpn.core.ProfileManager;
import de.blinkt.openvpn.core.VPNLaunchHelper;
import de.blinkt.openvpn.core.VpnStatus;
-import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus;
/**
* This Activity actually handles two stages of a launcher shortcut's life cycle.
@@ -64,6 +78,7 @@ public class LaunchVPN extends Activity {
public static final String EXTRA_NAME = "de.blinkt.openvpn.shortcutProfileName";
public static final String EXTRA_HIDELOG = "de.blinkt.openvpn.showNoLogWindow";
public static final String CLEARLOG = "clearlogconnect";
+ public static final String EXTRA_TEMP_VPN_PROFILE = "se.leap.bitmask.tempVpnProfile";
private static final int START_VPN_PROFILE = 70;
@@ -92,15 +107,17 @@ public class LaunchVPN extends Activity {
if (Intent.ACTION_MAIN.equals(action)) {
// Check if we need to clear the log
- if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean(CLEARLOG, true))
+ 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);
+ VpnProfile profileToConnect = (VpnProfile) intent.getExtras().getSerializable(EXTRA_TEMP_VPN_PROFILE);
+ if (profileToConnect == null)
+ profileToConnect = ProfileManager.get(this, shortcutUUID);
- VpnProfile profileToConnect = ProfileManager.get(this, shortcutUUID);
if (shortcutName != null && profileToConnect == null)
profileToConnect = ProfileManager.getInstance(this).getProfileByName(shortcutName);
@@ -118,24 +135,28 @@ public class LaunchVPN extends Activity {
@Override
protected void onActivityResult (int requestCode, int resultCode, Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
+ super.onActivityResult(requestCode, resultCode, data);
- if(requestCode==START_VPN_PROFILE) {
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
- boolean showLogWindow = prefs.getBoolean("showlogwindow", true);
+ if(requestCode==START_VPN_PROFILE) {
+ SharedPreferences prefs = Preferences.getDefaultSharedPreferences(this);
+ boolean showLogWindow = prefs.getBoolean("showlogwindow", true);
+
+ if(!mhideLog && showLogWindow)
+ showLogWindow();
+ ProfileManager.updateLRU(this, mSelectedProfile);
+ VPNLaunchHelper.startOpenVpn(mSelectedProfile, getBaseContext());
+ finish();
- if(!mhideLog && showLogWindow)
- showLogWindow();
+ } 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);
- 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);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
+ VpnStatus.logError(R.string.nought_alwayson_warning);
- finish();
- }
+ finish();
+ }
}
void showLogWindow() {
@@ -158,9 +179,27 @@ public class LaunchVPN extends Activity {
}
});
+ d.setOnCancelListener(new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ finish();
+ }
+ });
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1)
+ setOnDismissListener(d);
d.show();
}
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+ private void setOnDismissListener(AlertDialog.Builder d) {
+ d.setOnDismissListener(new DialogInterface.OnDismissListener() {
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ finish();
+ }
+ });
+ }
+
void launchVPN() {
int vpnok = mSelectedProfile.checkProfile(this);
if (vpnok != R.string.no_error_found) {
@@ -170,7 +209,7 @@ public class LaunchVPN extends Activity {
Intent intent = VpnService.prepare(this);
// Check if we want to fix /dev/tun
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ SharedPreferences prefs = Preferences.getDefaultSharedPreferences(this);
boolean usecm9fix = prefs.getBoolean("useCM9Fix", false);
boolean loadTunModule = prefs.getBoolean("loadTunModule", false);
diff --git a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java
index 38d76f68..aa25da48 100644
--- a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java
+++ b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java
@@ -11,7 +11,6 @@ import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
-import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
@@ -54,6 +53,7 @@ import javax.crypto.NoSuchPaddingException;
import de.blinkt.openvpn.core.Connection;
import de.blinkt.openvpn.core.NativeUtils;
import de.blinkt.openvpn.core.OpenVPNService;
+import de.blinkt.openvpn.core.PasswordCache;
import de.blinkt.openvpn.core.VPNLaunchHelper;
import de.blinkt.openvpn.core.VpnStatus;
import de.blinkt.openvpn.core.X509Utils;
@@ -73,14 +73,10 @@ 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 = 6;
- public static final int DEFAULT_MSSFIX_SIZE = 1450;
+ public static final int DEFAULT_MSSFIX_SIZE = 1280;
public static String DEFAULT_DNS1 = "8.8.8.8";
public static String DEFAULT_DNS2 = "8.8.4.4";
- public transient String mTransientPW = null;
- public transient String mTransientPCKS12PW = null;
-
-
public static final int TYPE_CERTIFICATES = 0;
public static final int TYPE_PKCS12 = 1;
public static final int TYPE_KEYSTORE = 2;
@@ -94,6 +90,12 @@ public class VpnProfile implements Serializable, Cloneable {
public static final int X509_VERIFY_TLSREMOTE_DN = 2;
public static final int X509_VERIFY_TLSREMOTE_RDN = 3;
public static final int X509_VERIFY_TLSREMOTE_RDN_PREFIX = 4;
+
+
+ public static final int AUTH_RETRY_NONE_FORGET = 0;
+ private static final int AUTH_RETRY_NONE_KEEP = 1;
+ public static final int AUTH_RETRY_NOINTERACT = 2;
+ private static final int AUTH_RETRY_INTERACT = 3;
// variable named wrong and should haven beeen transient
// but needs to keep wrong name to guarante loading of old
// profiles
@@ -137,11 +139,14 @@ public class VpnProfile implements Serializable, Cloneable {
public String mCustomRoutesv6 = "";
public String mKeyPassword = "";
public boolean mPersistTun = false;
- public String mConnectRetryMax = "5";
- public String mConnectRetry = "5";
+ public String mConnectRetryMax = "-1";
+ public String mConnectRetry = "2";
+ public String mConnectRetryMaxTime = "300";
public boolean mUserEditable = true;
public String mAuth = "";
public int mX509AuthType = X509_VERIFY_TLSREMOTE_RDN;
+ public String mx509UsernameField = null;
+
private transient PrivateKey mPrivateKey;
// Public attributes, since I got mad with getter/setter
// set members to default values
@@ -159,15 +164,25 @@ public class VpnProfile implements Serializable, Cloneable {
public String mCrlFilename;
public String mProfileCreator;
+ public int mAuthRetry = AUTH_RETRY_NONE_FORGET;
+ public int mTunMtu;
+
public boolean mPushPeerInfo = false;
public static final boolean mIsOpenVPN22 = false;
+ public int mVersion = 0;
+
+ // timestamp when the profile was last used
+ public long mLastUsed;
+
/* Options no longer used in new profiles */
- public String mServerName = "openvpn.blinkt.de";
+ public String mServerName = "openvpn.example.com";
public String mServerPort = "1194";
public boolean mUseUdp = true;
+
+
public VpnProfile(String name) {
mUuid = UUID.randomUUID();
mName = name;
@@ -175,6 +190,7 @@ public class VpnProfile implements Serializable, Cloneable {
mConnections = new Connection[1];
mConnections[0] = new Connection();
+ mLastUsed = System.currentTimeMillis();
}
public static String openVpnEscape(String unescaped) {
@@ -192,6 +208,17 @@ public class VpnProfile implements Serializable, Cloneable {
return '"' + escapedString + '"';
}
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof VpnProfile) {
+ VpnProfile vpnProfile = (VpnProfile) obj;
+ return mUuid.equals(vpnProfile.mUuid);
+ } else {
+ return false;
+ }
+ }
+
public void clearDefaults() {
mServerName = "unknown";
mUsePull = false;
@@ -212,7 +239,7 @@ public class VpnProfile implements Serializable, Cloneable {
}
public String getName() {
- if (mName == null)
+ if (TextUtils.isEmpty(mName))
return "No profile name";
return mName;
}
@@ -274,12 +301,13 @@ public class VpnProfile implements Serializable, Cloneable {
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,
+ String versionString = String.format(Locale.US, "%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";
+ cfg += "allow-recursive-routing\n";
// Users are confused by warnings that are misleading...
cfg += "ifconfig-nowarn\n";
@@ -299,19 +327,25 @@ public class VpnProfile implements Serializable, Cloneable {
cfg += "verb " + MAXLOGLEVEL + "\n";
if (mConnectRetryMax == null) {
- mConnectRetryMax = "5";
+ mConnectRetryMax = "-1";
}
if (!mConnectRetryMax.equals("-1"))
cfg += "connect-retry-max " + mConnectRetryMax + "\n";
- if (mConnectRetry == null)
- mConnectRetry = "5";
+ if (TextUtils.isEmpty(mConnectRetry))
+ mConnectRetry = "2";
+
+ if (TextUtils.isEmpty(mConnectRetryMaxTime))
+ mConnectRetryMaxTime = "300";
- if (!mIsOpenVPN22 || !mUseUdp)
+ if (!mIsOpenVPN22)
+ cfg += "connect-retry " + mConnectRetry + " " + mConnectRetryMaxTime + "\n";
+ else if (mIsOpenVPN22 && mUseUdp)
cfg += "connect-retry " + mConnectRetry + "\n";
+
cfg += "resolv-retry 60\n";
@@ -384,6 +418,12 @@ public class VpnProfile implements Serializable, Cloneable {
cfg += insertFileData("ca", mCaFilename);
}
+ if (isUserPWAuth())
+ {
+ if (mAuthenticationType == AUTH_RETRY_NOINTERACT)
+ cfg += "auth-retry nointeract";
+ }
+
if (!TextUtils.isEmpty(mCrlFilename))
cfg += insertFileData("crl-verify", mCrlFilename);
@@ -392,12 +432,16 @@ public class VpnProfile implements Serializable, Cloneable {
}
if (mUseTLSAuth) {
+ boolean useTlsCrypt = mTLSAuthDirection.equals("tls-crypt");
+
if (mAuthenticationType == TYPE_STATICKEYS)
cfg += insertFileData("secret", mTLSAuthFilename);
+ else if (useTlsCrypt)
+ cfg += insertFileData("tls-crypt", mTLSAuthFilename);
else
cfg += insertFileData("tls-auth", mTLSAuthFilename);
- if (!TextUtils.isEmpty(mTLSAuthDirection)) {
+ if (!TextUtils.isEmpty(mTLSAuthDirection) && !useTlsCrypt) {
cfg += "key-direction ";
cfg += mTLSAuthDirection;
cfg += "\n";
@@ -409,8 +453,12 @@ public class VpnProfile implements Serializable, Cloneable {
if (!TextUtils.isEmpty(mIPv4Address))
cfg += "ifconfig " + cidrToIPAndNetmask(mIPv4Address) + "\n";
- if (!TextUtils.isEmpty(mIPv6Address))
- cfg += "ifconfig-ipv6 " + mIPv6Address + "\n";
+ if (!TextUtils.isEmpty(mIPv6Address)) {
+ // Use our own ip as gateway since we ignore it anyway
+ String fakegw = mIPv6Address.split("/", 2)[0];
+ cfg += "ifconfig-ipv6 " + mIPv6Address + " " + fakegw +"\n";
+ }
+
}
if (mUsePull && mRoutenopull)
@@ -441,22 +489,33 @@ public class VpnProfile implements Serializable, Cloneable {
cfg += routes;
if (mOverrideDNS || !mUsePull) {
- if (!TextUtils.isEmpty(mDNS1))
- cfg += "dhcp-option DNS " + mDNS1 + "\n";
- if (!TextUtils.isEmpty(mDNS2))
- cfg += "dhcp-option DNS " + mDNS2 + "\n";
- if (!TextUtils.isEmpty(mSearchDomain))
+ if (!TextUtils.isEmpty(mDNS1)) {
+ if (mDNS1.contains(":"))
+ cfg += "dhcp-option DNS6 " + mDNS1 + "\n";
+ else
+ cfg += "dhcp-option DNS " + mDNS1 + "\n";
+ } if (!TextUtils.isEmpty(mDNS2)) {
+ if (mDNS2.contains(":"))
+ cfg += "dhcp-option DNS6 " + mDNS2 + "\n";
+ else
+ cfg += "dhcp-option DNS " + mDNS2 + "\n";
+ } if (!TextUtils.isEmpty(mSearchDomain))
cfg += "dhcp-option DOMAIN " + mSearchDomain + "\n";
}
if (mMssFix != 0) {
if (mMssFix != 1450) {
- cfg += String.format("mssfix %d\n", mMssFix, Locale.US);
+ cfg += String.format(Locale.US, "mssfix %d\n", mMssFix);
} else
cfg += "mssfix\n";
}
+ if (mTunMtu >= 48 && mTunMtu != 1500)
+ {
+ cfg+= String.format(Locale.US, "tun-mtu %d\n", mTunMtu);
+ }
+
if (mNobind)
cfg += "nobind\n";
@@ -465,7 +524,7 @@ public class VpnProfile implements Serializable, Cloneable {
if (mAuthenticationType != TYPE_STATICKEYS) {
if (mCheckRemoteCN) {
if (mRemoteCN == null || mRemoteCN.equals(""))
- cfg += "verify-x509-name " + mConnections[0].mServerName + " name\n";
+ cfg += "verify-x509-name " + openVpnEscape(mConnections[0].mServerName) + " name\n";
else
switch (mX509AuthType) {
@@ -488,6 +547,8 @@ public class VpnProfile implements Serializable, Cloneable {
cfg += "verify-x509-name " + openVpnEscape(mRemoteCN) + "\n";
break;
}
+ if (!TextUtils.isEmpty(mx509UsernameField))
+ cfg += "x509-username-field " + openVpnEscape(mx509UsernameField) + "\n";
}
if (mExpectTLSCert)
cfg += "remote-cert-tls server\n";
@@ -659,7 +720,7 @@ public class VpnProfile implements Serializable, Cloneable {
Intent intent = new Intent(context, OpenVPNService.class);
intent.putExtra(prefix + ".profileUUID", mUuid.toString());
-
+ intent.putExtra(prefix + ".profileVersion", mVersion);
return intent;
}
@@ -730,6 +791,10 @@ public class VpnProfile implements Serializable, Cloneable {
}
}
+ public void pwDidFail(Context c) {
+
+ }
+
class NoCertReturnedException extends Exception {
public NoCertReturnedException(String msg) {
@@ -738,6 +803,10 @@ public class VpnProfile implements Serializable, Cloneable {
}
synchronized String[] getKeyStoreCertificates(Context context, int tries) {
+ // Force application context- KeyChain methods will block long enough that by the time they
+ // are finished and try to unbind, the original activity context might have been destroyed.
+ context = context.getApplicationContext();
+
try {
PrivateKey privateKey = KeyChain.getPrivateKey(context, mAlias);
mPrivateKey = privateKey;
@@ -838,8 +907,14 @@ public class VpnProfile implements Serializable, Cloneable {
if (mAuthenticationType == TYPE_KEYSTORE || mAuthenticationType == TYPE_USERPASS_KEYSTORE) {
if (mAlias == null)
return R.string.no_keystore_cert_selected;
+ } else if (mAuthenticationType == TYPE_CERTIFICATES || mAuthenticationType == TYPE_USERPASS_CERTIFICATES){
+ if (TextUtils.isEmpty(mCaFilename))
+ return R.string.no_ca_cert_selected;
}
+ if (mCheckRemoteCN && mX509AuthType==X509_VERIFY_TLSREMOTE)
+ return R.string.deprecated_tls_remote;
+
if (!mUsePull || mAuthenticationType == TYPE_STATICKEYS) {
if (mIPv4Address == null || cidrToIPAndNetmask(mIPv4Address) == null)
return R.string.ipv4_format_error;
@@ -881,10 +956,9 @@ public class VpnProfile implements Serializable, Cloneable {
//! Openvpn asks for a "Private Key", this should be pkcs12 key
//
public String getPasswordPrivateKey() {
- if (mTransientPCKS12PW != null) {
- String pwcopy = mTransientPCKS12PW;
- mTransientPCKS12PW = null;
- return pwcopy;
+ String cachedPw = PasswordCache.getPKCS12orCertificatePassword(mUuid, true);
+ if (cachedPw != null) {
+ return cachedPw;
}
switch (mAuthenticationType) {
case TYPE_PKCS12:
@@ -949,33 +1023,32 @@ public class VpnProfile implements Serializable, Cloneable {
return false;
}
- public int needUserPWInput(boolean ignoreTransient) {
+ public int needUserPWInput(String transientCertOrPkcs12PW, String mTransientAuthPW) {
if ((mAuthenticationType == TYPE_PKCS12 || mAuthenticationType == TYPE_USERPASS_PKCS12) &&
(mPKCS12Password == null || mPKCS12Password.equals(""))) {
- if (ignoreTransient || mTransientPCKS12PW == null)
+ if (transientCertOrPkcs12PW == null)
return R.string.pkcs12_file_encryption_key;
}
if (mAuthenticationType == TYPE_CERTIFICATES || mAuthenticationType == TYPE_USERPASS_CERTIFICATES) {
if (requireTLSKeyPassword() && TextUtils.isEmpty(mKeyPassword))
- if (ignoreTransient || mTransientPCKS12PW == null) {
+ if (transientCertOrPkcs12PW == null) {
return R.string.private_key_password;
}
}
if (isUserPWAuth() &&
(TextUtils.isEmpty(mUsername) ||
- (TextUtils.isEmpty(mPassword) && (mTransientPW == null || ignoreTransient)))) {
+ (TextUtils.isEmpty(mPassword) && mTransientAuthPW == null))) {
return R.string.password;
}
return 0;
}
public String getPasswordAuth() {
- if (mTransientPW != null) {
- String pwcopy = mTransientPW;
- mTransientPW = null;
- return pwcopy;
+ String cachedPw = PasswordCache.getAuthPassword(mUuid, true);
+ if (cachedPw != null) {
+ return cachedPw;
} else {
return mPassword;
}
@@ -1008,7 +1081,6 @@ public class VpnProfile implements Serializable, Cloneable {
try {
-
/* ECB is perfectly fine in this special case, since we are using it for
the public/private part in the TLS exchange
*/
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 d25bccad..068821f5 100644
--- a/app/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java
+++ b/app/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java
@@ -7,33 +7,40 @@ package de.blinkt.openvpn.activities;
import android.app.Activity;
import android.app.AlertDialog;
-import android.content.*;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.ServiceConnection;
import android.os.IBinder;
+import android.os.RemoteException;
+import de.blinkt.openvpn.LaunchVPN;
import se.leap.bitmaskclient.R;
+import de.blinkt.openvpn.core.IOpenVPNServiceInternal;
import de.blinkt.openvpn.core.OpenVPNService;
import de.blinkt.openvpn.core.ProfileManager;
+import de.blinkt.openvpn.core.VpnStatus;
/**
* Created by arne on 13.10.13.
*/
public class DisconnectVPN extends Activity implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
- protected OpenVPNService mService;
-
+ private IOpenVPNServiceInternal mService;
private ServiceConnection mConnection = new ServiceConnection() {
+
@Override
public void onServiceConnected(ComponentName className,
IBinder service) {
- // We've bound to LocalService, cast the IBinder and get LocalService instance
- OpenVPNService.LocalBinder binder = (OpenVPNService.LocalBinder) service;
- mService = binder.getService();
+
+ mService = IOpenVPNServiceInternal.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
- mService =null;
+ mService = null;
}
};
@@ -53,24 +60,13 @@ public class DisconnectVPN extends Activity implements DialogInterface.OnClickLi
unbindService(mConnection);
}
- // if (getIntent() !=null && OpenVpnService.DISCONNECT_VPN.equals(getIntent().getAction()))
-
- // setIntent(null);
-
- /*
- @Override
- protected void onNewIntent(Intent intent) {
- super.onNewIntent(intent);
- setIntent(intent);
- }
- */
-
private void showDisconnectDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.title_cancel);
builder.setMessage(R.string.cancel_connection_query);
- builder.setNegativeButton(android.R.string.no, this);
- builder.setPositiveButton(android.R.string.yes,this);
+ builder.setNegativeButton(android.R.string.cancel, this);
+ builder.setPositiveButton(R.string.cancel_connection, this);
+ builder.setNeutralButton(R.string.reconnect, this);
builder.setOnCancelListener(this);
builder.show();
@@ -80,8 +76,18 @@ public class DisconnectVPN extends Activity implements DialogInterface.OnClickLi
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_POSITIVE) {
ProfileManager.setConntectedVpnProfileDisconnected(this);
- if (mService != null && mService.getManagement() != null)
- mService.getManagement().stopVPN(false);
+ if (mService != null) {
+ try {
+ mService.stopVPN(false);
+ } catch (RemoteException e) {
+ VpnStatus.logException(e);
+ }
+ }
+ } else if (which == DialogInterface.BUTTON_NEUTRAL) {
+ Intent intent = new Intent(this, LaunchVPN.class);
+ intent.putExtra(LaunchVPN.EXTRA_KEY, VpnStatus.getLastConnectedVPNProfile());
+ intent.setAction(Intent.ACTION_MAIN);
+ startActivity(intent);
}
finish();
}
diff --git a/app/src/main/java/de/blinkt/openvpn/api/APIVpnProfile.java b/app/src/main/java/de/blinkt/openvpn/api/APIVpnProfile.java
new file mode 100644
index 00000000..adc7f8b7
--- /dev/null
+++ b/app/src/main/java/de/blinkt/openvpn/api/APIVpnProfile.java
@@ -0,0 +1,60 @@
+/*
+ * 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.api;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class APIVpnProfile implements Parcelable {
+
+ public final String mUUID;
+ public final String mName;
+ public final boolean mUserEditable;
+ //public final String mProfileCreator;
+
+ public APIVpnProfile(Parcel in) {
+ mUUID = in.readString();
+ mName = in.readString();
+ mUserEditable = in.readInt() != 0;
+ //mProfileCreator = in.readString();
+ }
+
+ public APIVpnProfile(String uuidString, String name, boolean userEditable, String profileCreator) {
+ mUUID = uuidString;
+ mName = name;
+ mUserEditable = userEditable;
+ //mProfileCreator = profileCreator;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mUUID);
+ dest.writeString(mName);
+ if (mUserEditable)
+ dest.writeInt(0);
+ else
+ dest.writeInt(1);
+ //dest.writeString(mProfileCreator);
+ }
+
+ public static final Parcelable.Creator<APIVpnProfile> CREATOR
+ = new Parcelable.Creator<APIVpnProfile>() {
+ public APIVpnProfile createFromParcel(Parcel in) {
+ return new APIVpnProfile(in);
+ }
+
+ public APIVpnProfile[] newArray(int size) {
+ return new APIVpnProfile[size];
+ }
+ };
+
+
+}
diff --git a/app/src/main/java/de/blinkt/openvpn/core/CIDRIP.java b/app/src/main/java/de/blinkt/openvpn/core/CIDRIP.java
index 07f2152f..799c68c9 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/CIDRIP.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/CIDRIP.java
@@ -47,9 +47,9 @@ class CIDRIP {
public boolean normalise() {
long ip = getInt(mIp);
- long newip = ip & (0xffffffffl << (32 - len));
+ long newip = ip & (0xffffffffL << (32 - len));
if (newip != ip) {
- mIp = String.format("%d.%d.%d.%d", (newip & 0xff000000) >> 24, (newip & 0xff0000) >> 16, (newip & 0xff00) >> 8, newip & 0xff);
+ mIp = String.format(Locale.US,"%d.%d.%d.%d", (newip & 0xff000000) >> 24, (newip & 0xff0000) >> 16, (newip & 0xff00) >> 8, newip & 0xff);
return true;
} else {
return false;
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 d14e643e..74afd61e 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java
@@ -6,7 +6,7 @@
package de.blinkt.openvpn.core;
import android.text.TextUtils;
-import android.util.Pair;
+import android.support.v4.util.Pair;
import java.io.BufferedReader;
import java.io.IOException;
@@ -119,6 +119,9 @@ public class ConfigParser {
}
} while (true);
+ if(inlinefile.endsWith("\n"))
+ inlinefile = inlinefile.substring(0, inlinefile.length()-1);
+
args.clear();
args.add(argname);
args.add(inlinefile);
@@ -251,10 +254,12 @@ public class ConfigParser {
"route-up",
"route-pre-down",
"auth-user-pass-verify",
+ "block-outside-dns",
"dhcp-release",
"dhcp-renew",
"dh",
"group",
+ "allow-recursive-routing",
"ip-win32",
"management-hold",
"management",
@@ -273,6 +278,7 @@ public class ConfigParser {
"plugin",
"machine-readable-output",
"persist-key",
+ "push",
"register-dns",
"route-delay",
"route-gateway",
@@ -322,7 +328,6 @@ public class ConfigParser {
"socks-proxy",
"socks-proxy-retry",
"explicit-exit-notify",
- "mssfix"
};
@@ -394,7 +399,7 @@ public class ConfigParser {
np.mCustomRoutesv6 = customIPv6Routes;
}
- Vector<String> routeNoPull = getOption("route-nopull", 1, 1);
+ Vector<String> routeNoPull = getOption("route-nopull", 0, 0);
if (routeNoPull!=null)
np.mRoutenopull=true;
@@ -417,15 +422,21 @@ public class ConfigParser {
if (direction != null)
np.mTLSAuthDirection = direction.get(1);
- Vector<Vector<String>> defgw = getAllOption("redirect-gateway", 0, 5);
+ Vector<String> tlscrypt = getOption("tls-crypt", 1, 1);
+ if (tlscrypt!=null) {
+ np.mUseTLSAuth = true;
+ np.mTLSAuthFilename = tlscrypt.get(1);
+ np.mTLSAuthDirection = "tls-crypt";
+ }
+
+ Vector<Vector<String>> defgw = getAllOption("redirect-gateway", 0, 7);
if (defgw != null) {
- np.mUseDefaultRoute = true;
- checkRedirectParameters(np, defgw);
+ checkRedirectParameters(np, defgw, true);
}
Vector<Vector<String>> redirectPrivate = getAllOption("redirect-private", 0, 5);
if (redirectPrivate != null) {
- checkRedirectParameters(np, redirectPrivate);
+ checkRedirectParameters(np, redirectPrivate, false);
}
Vector<String> dev = getOption("dev", 1, 1);
Vector<String> devtype = getOption("dev-type", 1, 1);
@@ -448,11 +459,23 @@ public class ConfigParser {
throw new ConfigParseError("Argument to --mssfix has to be an integer");
}
} else {
- np.mMssFix = VpnProfile.DEFAULT_MSSFIX_SIZE;
+ np.mMssFix = 1450; // OpenVPN default size
}
}
+ Vector<String> tunmtu = getOption("mtu", 1, 1);
+
+ if (tunmtu != null) {
+ try {
+ np.mTunMtu = Integer.parseInt(tunmtu.get(1));
+ } catch (NumberFormatException e) {
+ throw new ConfigParseError("Argument to --tun-mtu has to be an integer");
+ }
+ }
+
+
+
Vector<String> mode = getOption("mode", 1, 1);
if (mode != null) {
if (!mode.get(1).equals("p2p"))
@@ -554,16 +577,23 @@ public class ConfigParser {
if (verifyx509name.size() > 2) {
if (verifyx509name.get(2).equals("name"))
np.mX509AuthType = VpnProfile.X509_VERIFY_TLSREMOTE_RDN;
+ else if (verifyx509name.get(2).equals("subject"))
+ np.mX509AuthType = VpnProfile.X509_VERIFY_TLSREMOTE_DN;
else if (verifyx509name.get(2).equals("name-prefix"))
np.mX509AuthType = VpnProfile.X509_VERIFY_TLSREMOTE_RDN_PREFIX;
else
- throw new ConfigParseError("Unknown parameter to x509-verify-name: " + verifyx509name.get(2));
+ throw new ConfigParseError("Unknown parameter to verify-x509-name: " + verifyx509name.get(2));
} else {
np.mX509AuthType = VpnProfile.X509_VERIFY_TLSREMOTE_DN;
}
}
+ Vector<String> x509usernamefield = getOption("x509-username-field", 1,1);
+ if (x509usernamefield!=null) {
+ np.mx509UsernameField = x509usernamefield.get(1);
+ }
+
Vector<String> verb = getOption("verb", 1, 1);
if (verb != null) {
@@ -580,9 +610,12 @@ public class ConfigParser {
if (getOption("push-peer-info", 0, 0) != null)
np.mPushPeerInfo = true;
- Vector<String> connectretry = getOption("connect-retry", 1, 1);
- if (connectretry != null)
+ Vector<String> connectretry = getOption("connect-retry", 1, 2);
+ if (connectretry != null) {
np.mConnectRetry = connectretry.get(1);
+ if (connectretry.size() > 2)
+ np.mConnectRetryMaxTime = connectretry.get(2);
+ }
Vector<String> connectretrymax = getOption("connect-retry-max", 1, 1);
if (connectretrymax != null)
@@ -613,6 +646,19 @@ public class ConfigParser {
}
}
+ Vector<String> authretry = getOption("auth-retry", 1, 1);
+ if (authretry != null) {
+ if (authretry.get(1).equals("none"))
+ np.mAuthRetry = VpnProfile.AUTH_RETRY_NONE_FORGET;
+ else if (authretry.get(1).equals("nointeract"))
+ np.mAuthRetry = VpnProfile.AUTH_RETRY_NOINTERACT;
+ else if (authretry.get(1).equals("interact"))
+ np.mAuthRetry = VpnProfile.AUTH_RETRY_NOINTERACT;
+ else
+ throw new ConfigParseError("Unknown parameter to auth-retry: " + authretry.get(2));
+ }
+
+
Vector<String> crlfile = getOption("crl-verify", 1, 2);
if (crlfile != null) {
// If the 'dir' parameter is present just add it as custom option ..
@@ -776,22 +822,34 @@ public class ConfigParser {
}
- private void checkRedirectParameters(VpnProfile np, Vector<Vector<String>> defgw) {
+ private void checkRedirectParameters(VpnProfile np, Vector<Vector<String>> defgw, boolean defaultRoute) {
+
+ boolean noIpv4 = false;
+ if (defaultRoute)
+
for (Vector<String> redirect : defgw)
for (int i = 1; i < redirect.size(); i++) {
if (redirect.get(i).equals("block-local"))
np.mAllowLocalLAN = false;
else if (redirect.get(i).equals("unblock-local"))
np.mAllowLocalLAN = true;
+ else if (redirect.get(i).equals("!ipv4"))
+ noIpv4=true;
+ else if (redirect.get(i).equals("ipv6"))
+ np.mUseDefaultRoutev6=true;
}
+ if (defaultRoute && !noIpv4)
+ np.mUseDefaultRoute=true;
}
private boolean isUdpProto(String proto) throws ConfigParseError {
boolean isudp;
- if (proto.equals("udp") || proto.equals("udp6"))
+ if (proto.equals("udp") || proto.equals("udp4") || proto.equals("udp6"))
isudp = true;
else if (proto.equals("tcp-client") ||
proto.equals("tcp") ||
+ proto.equals("tcp4") ||
+ proto.endsWith("tcp4-client") ||
proto.equals("tcp6") ||
proto.endsWith("tcp6-client"))
isudp = false;
@@ -858,10 +916,9 @@ public class ConfigParser {
for (Vector<String> optionsline : option) {
if (!ignoreThisOption(optionsline)) {
// Check if option had been inlined and inline again
- if (optionsline.size() == 2 && "extra-certs".equals(optionsline.get(0)) ) {
+ if (optionsline.size() == 2 &&
+ ("extra-certs".equals(optionsline.get(0)) || "http-proxy-user-pass".equals(optionsline.get(0)))) {
custom += VpnProfile.insertFileData(optionsline.get(0), optionsline.get(1));
-
-
} else {
for (String arg : optionsline)
custom += VpnProfile.openVpnEscape(arg) + " ";
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 3455450b..ff15daec 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/Connection.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/Connection.java
@@ -8,21 +8,23 @@ package de.blinkt.openvpn.core;
import android.text.TextUtils;
import java.io.Serializable;
+import java.util.Locale;
public class Connection implements Serializable, Cloneable {
- public String mServerName = "openvpn.blinkt.de";
+ public String mServerName = "openvpn.example.com";
public String mServerPort = "1194";
public boolean mUseUdp = true;
- public String mCustomConfiguration="";
- public boolean mUseCustomConfig=false;
- public boolean mEnabled=true;
+ public String mCustomConfiguration = "";
+ public boolean mUseCustomConfig = false;
+ public boolean mEnabled = true;
public int mConnectTimeout = 0;
+ public static final int CONNECTION_DEFAULT_TIMEOUT = 120;
private static final long serialVersionUID = 92031902903829089L;
public String getConnectionBlock() {
- String cfg="";
+ String cfg = "";
// Server Address
cfg += "remote ";
@@ -34,8 +36,8 @@ public class Connection implements Serializable, Cloneable {
else
cfg += " tcp-client\n";
- if (mConnectTimeout!=0)
- cfg += String.format(" connect-timeout %d\n" , mConnectTimeout);
+ if (mConnectTimeout != 0)
+ cfg += String.format(Locale.US, " connect-timeout %d\n", mConnectTimeout);
if (!TextUtils.isEmpty(mCustomConfiguration) && mUseCustomConfig) {
@@ -53,4 +55,11 @@ public class Connection implements Serializable, Cloneable {
public boolean isOnlyRemote() {
return TextUtils.isEmpty(mCustomConfiguration) || !mUseCustomConfig;
}
+
+ public int getTimeout() {
+ if (mConnectTimeout <= 0)
+ return CONNECTION_DEFAULT_TIMEOUT;
+ else
+ return mConnectTimeout;
+ }
}
diff --git a/app/src/main/java/de/blinkt/openvpn/core/ConnectionStatus.java b/app/src/main/java/de/blinkt/openvpn/core/ConnectionStatus.java
new file mode 100644
index 00000000..03d842e3
--- /dev/null
+++ b/app/src/main/java/de/blinkt/openvpn/core/ConnectionStatus.java
@@ -0,0 +1,47 @@
+/*
+ * 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.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Created by arne on 08.11.16.
+ */
+public enum ConnectionStatus implements Parcelable {
+ LEVEL_CONNECTED,
+ LEVEL_VPNPAUSED,
+ LEVEL_CONNECTING_SERVER_REPLIED,
+ LEVEL_CONNECTING_NO_SERVER_REPLY_YET,
+ LEVEL_NONETWORK,
+ LEVEL_NOTCONNECTED,
+ LEVEL_START,
+ LEVEL_AUTH_FAILED,
+ LEVEL_WAITING_FOR_USER_INPUT,
+ UNKNOWN_LEVEL;
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(ordinal());
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<ConnectionStatus> CREATOR = new Creator<ConnectionStatus>() {
+ @Override
+ public ConnectionStatus createFromParcel(Parcel in) {
+ return ConnectionStatus.values()[in.readInt()];
+ }
+
+ @Override
+ public ConnectionStatus[] newArray(int size) {
+ return new ConnectionStatus[size];
+ }
+ };
+}
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 40684af3..c396f181 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java
@@ -20,6 +20,7 @@ import de.blinkt.openvpn.core.VpnStatus.ByteCountListener;
import java.util.LinkedList;
import java.util.Objects;
+import java.util.StringTokenizer;
import static de.blinkt.openvpn.core.OpenVPNManagement.pauseReason;
@@ -64,13 +65,13 @@ public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountL
return shouldBeConnected();
}
- enum connectState {
+ private enum connectState {
SHOULDBECONNECTED,
PENDINGDISCONNECT,
DISCONNECTED
}
- static class Datapoint {
+ private static class Datapoint {
private Datapoint(long t, long d) {
timestamp = t;
data = d;
@@ -80,7 +81,7 @@ public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountL
long data;
}
- LinkedList<Datapoint> trafficdata = new LinkedList<DeviceStateReceiver.Datapoint>();
+ private LinkedList<Datapoint> trafficdata = new LinkedList<>();
@Override
@@ -102,7 +103,7 @@ public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountL
if (windowtraffic < TRAFFIC_LIMIT) {
screen = connectState.DISCONNECTED;
VpnStatus.logInfo(R.string.screenoff_pause,
- OpenVPNService.humanReadableByteCount(TRAFFIC_LIMIT, false), TRAFFIC_WINDOW);
+ "64 kB", TRAFFIC_WINDOW);
mManagement.pause(getPauseReason());
}
@@ -135,7 +136,7 @@ public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountL
@Override
public void onReceive(Context context, Intent intent) {
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ SharedPreferences prefs = Preferences.getDefaultSharedPreferences(context);
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
@@ -173,15 +174,15 @@ 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) {
NetworkInfo networkInfo = getCurrentNetworkInfo(context);
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ SharedPreferences prefs = Preferences.getDefaultSharedPreferences(context);
boolean sendusr1 = prefs.getBoolean("netchangereconnect", true);
@@ -261,6 +262,8 @@ public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountL
if (!netstatestring.equals(lastStateMsg))
VpnStatus.logInfo(R.string.netstatus, netstatestring);
+ VpnStatus.logDebug(String.format("Debug state info: %s, pause: %s, shouldbeconnected: %s, network: %s ",
+ netstatestring, getPauseReason(), shouldBeConnected(), network));
lastStateMsg = netstatestring;
}
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 db3ae751..e7019f42 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java
@@ -4,7 +4,14 @@
*/
package de.blinkt.openvpn.core;
+
+import android.annotation.TargetApi;
import android.app.Application;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.graphics.Color;
+import android.os.Build;
/*
import org.acra.ACRA;
@@ -12,32 +19,50 @@ import org.acra.ReportingInteractionMode;
import org.acra.annotation.ReportsCrashes;
*/
-import se.leap.bitmaskclient.BuildConfig;
import se.leap.bitmaskclient.R;
-import de.blinkt.openvpn.core.PRNGFixes;
-/*
-@ReportsCrashes(
- formKey = "",
- formUri = "http://reports.blinkt.de/report-icsopenvpn",
- reportType = org.acra.sender.HttpSender.Type.JSON,
- httpMethod = org.acra.sender.HttpSender.Method.PUT,
- formUriBasicAuthLogin="report-icsopenvpn",
- formUriBasicAuthPassword="Tohd4neiF9Ai!!!!111eleven",
- mode = ReportingInteractionMode.TOAST,
- resToastText = R.string.crash_toast_text
-)
-*/
public class ICSOpenVPNApplication extends Application {
+ private StatusListener mStatus;
+
@Override
public void onCreate() {
super.onCreate();
PRNGFixes.apply();
- if (BuildConfig.DEBUG) {
- //ACRA.init(this);
- }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
+
+ createNotificationChannels();
+ mStatus = new StatusListener();
+ mStatus.init(getApplicationContext());
+
+ }
+
+ @TargetApi(Build.VERSION_CODES.O)
+ private void createNotificationChannels() {
+ NotificationManager mNotificationManager =
+ (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+
+ // Background message
+ CharSequence name = getString(R.string.channel_name_background);
+ NotificationChannel mChannel = new NotificationChannel(OpenVPNService.NOTIFICATION_CHANNEL_BG_ID,
+ name, NotificationManager.IMPORTANCE_MIN);
+
+ mChannel.setDescription(getString(R.string.channel_description_background));
+ mChannel.enableLights(false);
+
+ mChannel.setLightColor(Color.DKGRAY);
+ mNotificationManager.createNotificationChannel(mChannel);
+
+ // Connection status change messages
+
+ name = getString(R.string.channel_name_status);
+ mChannel = new NotificationChannel(OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID,
+ name, NotificationManager.IMPORTANCE_DEFAULT);
+
+ mChannel.setDescription(getString(R.string.channel_description_status));
+ mChannel.enableLights(true);
- VpnStatus.initLogCache(getApplicationContext().getCacheDir());
+ mChannel.setLightColor(Color.BLUE);
+ mNotificationManager.createNotificationChannel(mChannel);
}
}
diff --git a/app/src/main/java/de/blinkt/openvpn/core/LogFileHandler.java b/app/src/main/java/de/blinkt/openvpn/core/LogFileHandler.java
index 288c7934..57d1fb22 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/LogFileHandler.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/LogFileHandler.java
@@ -8,7 +8,6 @@ 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;
@@ -16,6 +15,10 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.util.Locale;
@@ -29,7 +32,8 @@ class LogFileHandler extends Handler {
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 int MAGIC_BYTE = 0x55;
+ protected OutputStream mLogFile;
public static final String LOGFILE_NAME = "logcache.dat";
@@ -47,20 +51,20 @@ class LogFileHandler extends Handler {
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) {
+ } else if (msg.what == LOG_MESSAGE && msg.obj instanceof LogItem) {
// Ignore log messages if not yet initialized
if (mLogFile == null)
return;
- writeLogItemToDisk((VpnStatus.LogItem) msg.obj);
+ writeLogItemToDisk((LogItem) msg.obj);
} else if (msg.what == TRIM_LOG_FILE) {
trimLogFile();
- for (VpnStatus.LogItem li : VpnStatus.getlogbuffer())
+ for (LogItem li : VpnStatus.getlogbuffer())
writeLogItemToDisk(li);
} else if (msg.what == FLUSH_TO_DISK) {
flushToDisk();
}
- } catch (IOException e) {
+ } catch (IOException | BufferOverflowException e) {
e.printStackTrace();
VpnStatus.logError("Error during log cache: " + msg.what);
VpnStatus.logException(e);
@@ -72,95 +76,160 @@ class LogFileHandler extends Handler {
mLogFile.flush();
}
- private static void trimLogFile() {
+ private void trimLogFile() {
try {
mLogFile.flush();
- mLogFile.getChannel().truncate(0);
+ ((FileOutputStream) 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);
+ private void writeLogItemToDisk(LogItem li) throws IOException {
+
// 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();
+ byte[] liBytes = li.getMarschaledBytes();
+
+ writeEscapedBytes(liBytes);
+ }
+
+ public void writeEscapedBytes(byte[] bytes) throws IOException {
+ int magic = 0;
+ for (byte b : bytes)
+ if (b == MAGIC_BYTE || b == MAGIC_BYTE + 1)
+ magic++;
+
+ byte eBytes[] = new byte[bytes.length + magic];
+
+ int i = 0;
+ for (byte b : bytes) {
+ if (b == MAGIC_BYTE || b == MAGIC_BYTE + 1) {
+ eBytes[i++] = MAGIC_BYTE + 1;
+ eBytes[i++] = (byte) (b - MAGIC_BYTE);
+ } else {
+ eBytes[i++] = b;
+ }
+ }
+
+ byte[] lenBytes = ByteBuffer.allocate(4).putInt(bytes.length).array();
+ synchronized (mLogFile) {
+ mLogFile.write(MAGIC_BYTE);
+ mLogFile.write(lenBytes);
+ mLogFile.write(eBytes);
+ }
}
- private void openLogFile (File cacheDir) throws FileNotFoundException {
+ 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);
-
+ try {
+ File logfile = new File(cacheDir, LOGFILE_NAME);
- if (!logfile.exists() || !logfile.canRead())
- return;
+ if (!logfile.exists() || !logfile.canRead())
+ return;
+ readCacheContents(new FileInputStream(logfile));
- try {
+ } catch (java.io.IOException | java.lang.RuntimeException e) {
+ VpnStatus.logError("Reading cached logfile failed");
+ VpnStatus.logException(e);
+ e.printStackTrace();
+ // ignore reading file error
+ } finally {
+ synchronized (VpnStatus.readFileLock) {
+ VpnStatus.readFileLog = true;
+ VpnStatus.readFileLock.notifyAll();
+ }
+ }
+ }
- BufferedInputStream logFile = new BufferedInputStream(new FileInputStream(logfile));
- byte[] buf = new byte[8192];
- int read = logFile.read(buf, 0, 4);
- int itemsRead=0;
+ protected void readCacheContents(InputStream in) throws IOException {
+ BufferedInputStream logFile = new BufferedInputStream(in);
- while (read >= 4) {
- int len = ByteBuffer.wrap(buf, 0, 4).asIntBuffer().get();
+ byte[] buf = new byte[16384];
+ int read = logFile.read(buf, 0, 5);
+ int itemsRead = 0;
- // 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))));
+ readloop:
+ while (read >= 5) {
+ int skipped = 0;
+ while (buf[skipped] != MAGIC_BYTE) {
+ skipped++;
+ if (!(logFile.read(buf, skipped + 4, 1) == 1) || skipped + 10 > buf.length) {
+ VpnStatus.logDebug(String.format(Locale.US, "Skipped %d bytes and no a magic byte found", skipped));
+ break readloop;
}
- 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;
+ }
+ if (skipped > 0)
+ VpnStatus.logDebug(String.format(Locale.US, "Skipped %d bytes before finding a magic byte", skipped));
+
+ int len = ByteBuffer.wrap(buf, skipped + 1, 4).asIntBuffer().get();
+
+ // Marshalled LogItem
+ int pos = 0;
+ byte buf2[] = new byte[buf.length];
+
+ while (pos < len) {
+ byte b = (byte) logFile.read();
+ if (b == MAGIC_BYTE) {
+ VpnStatus.logDebug(String.format(Locale.US, "Unexpected magic byte found at pos %d, abort current log item", pos));
+ read = logFile.read(buf, 1, 4) + 1;
+ continue readloop;
+ } else if (b == MAGIC_BYTE + 1) {
+ b = (byte) logFile.read();
+ if (b == 0)
+ b = MAGIC_BYTE;
+ else if (b == 1)
+ b = MAGIC_BYTE + 1;
+ else {
+ VpnStatus.logDebug(String.format(Locale.US, "Escaped byte not 0 or 1: %d", b));
+ read = logFile.read(buf, 1, 4) + 1;
+ continue readloop;
+ }
}
+ buf2[pos++] = b;
+ }
+
+ restoreLogItem(buf2, len);
+ //Next item
+ read = logFile.read(buf, 0, 5);
+ itemsRead++;
+ if (itemsRead > 2 * VpnStatus.MAXLOGENTRIES) {
+ VpnStatus.logError("Too many logentries read from cache, aborting.");
+ read = 0;
}
- VpnStatus.logDebug(R.string.reread_log, itemsRead);
+ }
+ VpnStatus.logDebug(R.string.reread_log, itemsRead);
+ }
+ protected void restoreLogItem(byte[] buf, int len) throws UnsupportedEncodingException {
- } catch (java.io.IOException | java.lang.RuntimeException e) {
- VpnStatus.logError("Reading cached logfile failed");
- VpnStatus.logException(e);
- e.printStackTrace();
- // ignore reading file error
+ LogItem li = new LogItem(buf, len);
+ if (li.verify()) {
+ VpnStatus.newLogItem(li, true);
+ } else {
+ VpnStatus.logError(String.format(Locale.getDefault(),
+ "Could not read log item from file: %d: %s",
+ len, bytesToHex(buf, Math.max(len, 80))));
}
}
- final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
+ private final 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++ ) {
+ 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];
diff --git a/app/src/main/java/de/blinkt/openvpn/core/LogItem.java b/app/src/main/java/de/blinkt/openvpn/core/LogItem.java
index 6aefbb2e..cd048f4a 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/LogItem.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/LogItem.java
@@ -12,12 +12,10 @@ 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.BufferOverflowException;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -89,7 +87,7 @@ public class LogItem implements Parcelable {
}
- public byte[] getMarschaledBytes() throws UnsupportedEncodingException {
+ public byte[] getMarschaledBytes() throws UnsupportedEncodingException, BufferOverflowException {
ByteBuffer bb = ByteBuffer.allocate(16384);
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 eb6d4d42..37689b3f 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java
@@ -34,8 +34,8 @@ public class NetworkSpace {
/**
* sorts the networks with following criteria:
- * 1. compares first 1 of the network
- * 2. smaller networks are returned as smaller
+ * 1. compares first 1 of the network
+ * 2. smaller networks are returned as smaller
*/
@Override
public int compareTo(@NonNull ipAddress another) {
@@ -55,8 +55,7 @@ public class NetworkSpace {
/**
* Warning ignores the included integer
*
- * @param o
- * the object to compare this instance with.
+ * @param o the object to compare this instance with.
*/
@Override
public boolean equals(Object o) {
@@ -89,15 +88,15 @@ public class NetworkSpace {
}
public BigInteger getLastAddress() {
- if(lastAddress ==null)
+ if (lastAddress == null)
lastAddress = getMaskedAddress(true);
return lastAddress;
}
public BigInteger getFirstAddress() {
- if (firstAddress ==null)
- firstAddress =getMaskedAddress(false);
+ if (firstAddress == null)
+ firstAddress = getMaskedAddress(false);
return firstAddress;
}
@@ -126,7 +125,7 @@ public class NetworkSpace {
public String toString() {
//String in = included ? "+" : "-";
if (isV4)
- return String.format(Locale.US,"%s/%d", getIPv4Address(), networkMask);
+ return String.format(Locale.US, "%s/%d", getIPv4Address(), networkMask);
else
return String.format(Locale.US, "%s/%d", getIPv6Address(), networkMask);
}
@@ -142,36 +141,48 @@ public class NetworkSpace {
public ipAddress[] split() {
ipAddress firstHalf = new ipAddress(getFirstAddress(), networkMask + 1, included, isV4);
ipAddress secondHalf = new ipAddress(firstHalf.getLastAddress().add(BigInteger.ONE), networkMask + 1, included, isV4);
- if (BuildConfig.DEBUG) Assert.assertTrue(secondHalf.getLastAddress().equals(getLastAddress()));
+ if (BuildConfig.DEBUG)
+ Assert.assertTrue(secondHalf.getLastAddress().equals(getLastAddress()));
return new ipAddress[]{firstHalf, secondHalf};
}
String getIPv4Address() {
if (BuildConfig.DEBUG) {
- Assert.assertTrue (isV4);
- Assert.assertTrue (netAddress.longValue() <= 0xffffffffl);
- Assert.assertTrue (netAddress.longValue() >= 0);
+ Assert.assertTrue(isV4);
+ Assert.assertTrue(netAddress.longValue() <= 0xffffffffl);
+ Assert.assertTrue(netAddress.longValue() >= 0);
}
long ip = netAddress.longValue();
return String.format(Locale.US, "%d.%d.%d.%d", (ip >> 24) % 256, (ip >> 16) % 256, (ip >> 8) % 256, ip % 256);
}
String getIPv6Address() {
- if (BuildConfig.DEBUG) Assert.assertTrue (!isV4);
+ if (BuildConfig.DEBUG) Assert.assertTrue(!isV4);
BigInteger r = netAddress;
- Vector<String> parts = new Vector<String>();
- while (r.compareTo(BigInteger.ZERO) == 1 || parts.size() <3) {
+ String ipv6str = null;
+ boolean lastPart = true;
+
+ while (r.compareTo(BigInteger.ZERO) == 1) {
+
long part = r.mod(BigInteger.valueOf(0x10000)).longValue();
- if (part!=0)
- parts.add(0, String.format(Locale.US, "%x", part));
- else
- parts.add(0, "");
+ if (ipv6str != null || part != 0) {
+ if (ipv6str == null && !lastPart)
+ ipv6str = ":";
+
+ if (lastPart)
+ ipv6str = String.format(Locale.US, "%x", part, ipv6str);
+ else
+ ipv6str = String.format(Locale.US, "%x:%s", part, ipv6str);
+ }
+
r = r.shiftRight(16);
+ lastPart = false;
}
- String ipv6str = TextUtils.join(":", parts);
- while (ipv6str.contains(":::"))
- ipv6str = ipv6str.replace(":::", "::");
+ if (ipv6str == null)
+ return "::";
+
+
return ipv6str;
}
@@ -183,8 +194,8 @@ public class NetworkSpace {
BigInteger netLast = network.getLastAddress();
boolean a = ourFirst.compareTo(netFirst) != 1;
- boolean b = ourLast.compareTo(netLast) != -1;
- return a && b;
+ boolean b = ourLast.compareTo(netLast) != -1;
+ return a && b;
}
}
@@ -215,7 +226,7 @@ public class NetworkSpace {
public void addIPSplit(CIDRIP cidrIp, boolean include) {
ipAddress newIP = new ipAddress(cidrIp, include);
ipAddress[] splitIps = newIP.split();
- for (ipAddress split: splitIps)
+ for (ipAddress split : splitIps)
mIpAddresses.add(split);
}
@@ -229,16 +240,16 @@ public class NetworkSpace {
TreeSet<ipAddress> ipsDone = new TreeSet<ipAddress>();
- ipAddress currentNet = networks.poll();
- if (currentNet==null)
+ ipAddress currentNet = networks.poll();
+ if (currentNet == null)
return ipsDone;
- while (currentNet!=null) {
+ while (currentNet != null) {
// Check if it and the next of it are compatible
ipAddress nextNet = networks.poll();
if (BuildConfig.DEBUG) Assert.assertNotNull(currentNet);
- if (nextNet== null || currentNet.getLastAddress().compareTo(nextNet.getFirstAddress()) == -1) {
+ if (nextNet == null || currentNet.getLastAddress().compareTo(nextNet.getFirstAddress()) == -1) {
// Everything good, no overlapping nothing to do
ipsDone.add(currentNet);
@@ -249,7 +260,7 @@ public class NetworkSpace {
if (currentNet.included == nextNet.included) {
// Included in the next next and same type
// Simply forget our current network
- currentNet=nextNet;
+ currentNet = nextNet;
} else {
// our currentNet is included in next and types differ. Need to split the next network
ipAddress[] newNets = nextNet.split();
@@ -262,7 +273,8 @@ public class NetworkSpace {
networks.add(newNets[1]);
if (newNets[0].getLastAddress().equals(currentNet.getLastAddress())) {
- if (BuildConfig.DEBUG) Assert.assertEquals (newNets[0].networkMask, currentNet.networkMask);
+ if (BuildConfig.DEBUG)
+ Assert.assertEquals(newNets[0].networkMask, currentNet.networkMask);
// Don't add the lower half that would conflict with currentNet
} else {
if (!networks.contains(newNets[0]))
@@ -273,8 +285,8 @@ public class NetworkSpace {
} else {
if (BuildConfig.DEBUG) {
Assert.assertTrue(currentNet.networkMask < nextNet.networkMask);
- Assert.assertTrue (nextNet.getFirstAddress().compareTo(currentNet.getFirstAddress()) == 1);
- Assert.assertTrue (currentNet.getLastAddress().compareTo(nextNet.getLastAddress()) != -1);
+ Assert.assertTrue(nextNet.getFirstAddress().compareTo(currentNet.getFirstAddress()) == 1);
+ Assert.assertTrue(currentNet.getLastAddress().compareTo(nextNet.getLastAddress()) != -1);
}
// This network is bigger than the next and last ip of current >= next
@@ -289,8 +301,8 @@ public class NetworkSpace {
if (newNets[1].networkMask == nextNet.networkMask) {
if (BuildConfig.DEBUG) {
- Assert.assertTrue (newNets[1].getFirstAddress().equals(nextNet.getFirstAddress()));
- Assert.assertTrue (newNets[1].getLastAddress().equals(currentNet.getLastAddress()));
+ Assert.assertTrue(newNets[1].getFirstAddress().equals(nextNet.getFirstAddress()));
+ Assert.assertTrue(newNets[1].getLastAddress().equals(currentNet.getLastAddress()));
// split second equal the next network, do not add it
}
networks.add(nextNet);
@@ -322,20 +334,20 @@ public class NetworkSpace {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
// Include postive routes from the original set under < 4.4 since these might overrule the local
// network but only if no smaller negative route exists
- for(ipAddress origIp: mIpAddresses){
+ for (ipAddress origIp : mIpAddresses) {
if (!origIp.included)
continue;
// The netspace exists
- if(ipsSorted.contains(origIp))
+ if (ipsSorted.contains(origIp))
continue;
- boolean skipIp=false;
+ boolean skipIp = false;
// If there is any smaller net that is excluded we may not add the positive route back
- for (ipAddress calculatedIp: ipsSorted) {
- if(!calculatedIp.included && origIp.containsNet(calculatedIp)) {
- skipIp=true;
+ for (ipAddress calculatedIp : ipsSorted) {
+ if (!calculatedIp.included && origIp.containsNet(calculatedIp)) {
+ skipIp = true;
break;
}
}
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 2911fb1e..ef17e98b 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java
@@ -13,7 +13,7 @@ public interface OpenVPNManagement {
enum pauseReason {
noNetwork,
userPause,
- screenOff
+ screenOff,
}
int mBytecountInterval = 2;
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 2917bce1..5faa1de4 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java
@@ -16,17 +16,21 @@ 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.VpnService;
-import android.os.Binder;
import android.os.Build;
+import android.os.Bundle;
import android.os.Handler;
import android.os.Handler.Callback;
import android.os.IBinder;
import android.os.Message;
import android.os.ParcelFileDescriptor;
-import android.preference.PreferenceManager;
+import android.os.RemoteException;
+import android.support.annotation.NonNull;
+import android.support.annotation.RequiresApi;
import android.system.OsConstants;
import android.text.TextUtils;
import android.util.Log;
@@ -39,38 +43,38 @@ import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collection;
-import java.util.HashMap;
import java.util.Locale;
import java.util.Vector;
import se.leap.bitmaskclient.BuildConfig;
+import de.blinkt.openvpn.LaunchVPN;
import se.leap.bitmaskclient.R;
import de.blinkt.openvpn.VpnProfile;
import de.blinkt.openvpn.activities.DisconnectVPN;
import de.blinkt.openvpn.core.VpnStatus.ByteCountListener;
-import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus;
import de.blinkt.openvpn.core.VpnStatus.StateListener;
+import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_CONNECTED;
+import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT;
import static de.blinkt.openvpn.core.NetworkSpace.ipAddress;
-import static de.blinkt.openvpn.core.VpnStatus.ConnectionStatus.LEVEL_CONNECTED;
-import static de.blinkt.openvpn.core.VpnStatus.ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET;
-import static de.blinkt.openvpn.core.VpnStatus.ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT;
import se.leap.bitmaskclient.Dashboard;
-public class OpenVPNService extends VpnService implements StateListener, Callback, ByteCountListener {
+public class OpenVPNService extends VpnService implements StateListener, Callback, ByteCountListener, IOpenVPNServiceInternal {
public static final String START_SERVICE = "de.blinkt.openvpn.START_SERVICE";
public static final String START_SERVICE_STICKY = "de.blinkt.openvpn.START_SERVICE_STICKY";
public static final String ALWAYS_SHOW_NOTIFICATION = "de.blinkt.openvpn.NOTIFICATION_ALWAYS_VISIBLE";
public static final String DISCONNECT_VPN = "de.blinkt.openvpn.DISCONNECT_VPN";
private static final String PAUSE_VPN = "de.blinkt.openvpn.PAUSE_VPN";
private static final String RESUME_VPN = "se.leap.bitmaskclient.RESUME_VPN";
- private static final int OPENVPN_STATUS = 1;
+ public static final String NOTIFICATION_CHANNEL_BG_ID = "openvpn_bg";
+ public static final String NOTIFICATION_CHANNEL_NEWSTATUS_ID = "openvpn_newstat";
+ private String lastChannel;
+
private static boolean mNotificationAlwaysVisible = false;
private final Vector<String> mDnslist = new Vector<>();
private final NetworkSpace mRoutes = new NetworkSpace();
private final NetworkSpace mRoutesv6 = new NetworkSpace();
- private final IBinder mBinder = new LocalBinder();
private Thread mProcessThread = null;
private VpnProfile mProfile;
private String mDomain = null;
@@ -90,20 +94,65 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
private Toast mlastToast;
private Runnable mOpenVPNThread;
+ private static final int PRIORITY_MIN = -2;
+ private static final int PRIORITY_DEFAULT = 0;
+ private static final int PRIORITY_MAX = 2;
+
+
+ private final IBinder mBinder = new IOpenVPNServiceInternal.Stub() {
+
+ @Override
+ public boolean protect(int fd) throws RemoteException {
+ return OpenVPNService.this.protect(fd);
+ }
+
+ @Override
+ public void userPause(boolean shouldbePaused) throws RemoteException {
+ OpenVPNService.this.userPause(shouldbePaused);
+ }
+
+ @Override
+ public boolean stopVPN(boolean replaceConnection) throws RemoteException {
+ return OpenVPNService.this.stopVPN(replaceConnection);
+ }
+ };
+
// From: http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java
- public static String humanReadableByteCount(long bytes, boolean mbit) {
- if (mbit)
+ public static String humanReadableByteCount(long bytes, boolean speed, Resources res) {
+ if (speed)
bytes = bytes * 8;
- int unit = mbit ? 1000 : 1024;
- if (bytes < unit)
- return bytes + (mbit ? " bit" : " B");
-
- int exp = (int) (Math.log(bytes) / Math.log(unit));
- String pre = (mbit ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (mbit ? "" : "");
- if (mbit)
- return String.format(Locale.getDefault(), "%.1f %sbit", bytes / Math.pow(unit, exp), pre);
+ int unit = speed ? 1000 : 1024;
+
+
+ int exp = Math.max(0, Math.min((int) (Math.log(bytes) / Math.log(unit)), 3));
+
+ float bytesUnit = (float) (bytes / Math.pow(unit, exp));
+
+ if (speed)
+ switch (exp) {
+ case 0:
+ return res.getString(R.string.bits_per_second, bytesUnit);
+ case 1:
+ return res.getString(R.string.kbits_per_second, bytesUnit);
+ case 2:
+ return res.getString(R.string.mbits_per_second, bytesUnit);
+ default:
+ return res.getString(R.string.gbits_per_second, bytesUnit);
+ }
else
- return String.format(Locale.getDefault(), "%.1f %sB", bytes / Math.pow(unit, exp), pre);
+ switch (exp) {
+ case 0:
+ return res.getString(R.string.volume_byte, bytesUnit);
+ case 1:
+ return res.getString(R.string.volume_kbyte, bytesUnit);
+ case 2:
+ return res.getString(R.string.volume_mbyte, bytesUnit);
+ default:
+ return res.getString(R.string.volume_gbyte, bytesUnit);
+
+ }
+
+
}
@Override
@@ -117,7 +166,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
@Override
public void onRevoke() {
- VpnStatus.logInfo(R.string.permission_revoked);
+ VpnStatus.logError(R.string.permission_revoked);
mManagement.stopVPN(false);
endVpnService();
}
@@ -145,26 +194,33 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
}
}
- private void showNotification(final String msg, String tickerText, boolean lowpriority, long when, ConnectionStatus status) {
- String ns = Context.NOTIFICATION_SERVICE;
- NotificationManager mNotificationManager = (NotificationManager) getSystemService(ns);
-
+ private void showNotification(final String msg, String tickerText, @NonNull String channel, long when, ConnectionStatus status) {
+ NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
int icon = getIconByConnectionStatus(status);
android.app.Notification.Builder nbuilder = new Notification.Builder(this);
+ int priority;
+ if (channel.equals(NOTIFICATION_CHANNEL_BG_ID))
+ priority = PRIORITY_MIN;
+ else
+ priority = PRIORITY_DEFAULT;
+
if (mProfile != null)
- nbuilder.setContentTitle(getString(R.string.notifcation_title, mProfile.mName));
+ nbuilder.setContentTitle(getString(R.string.notifcation_title_bitmask, mProfile.mName));
else
nbuilder.setContentTitle(getString(R.string.notifcation_title_notconnect));
nbuilder.setContentText(msg);
nbuilder.setOnlyAlertOnce(true);
nbuilder.setOngoing(true);
- nbuilder.setContentIntent(getLogPendingIntent());
- nbuilder.setSmallIcon(icon);
+ nbuilder.setSmallIcon(icon);
+ if (status == LEVEL_WAITING_FOR_USER_INPUT)
+ nbuilder.setContentIntent(getUserInputIntent(msg));
+ else
+ nbuilder.setContentIntent(getGraphPendingIntent());
if (when != 0)
nbuilder.setWhen(when);
@@ -172,23 +228,39 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
// Try to set the priority available since API 16 (Jellybean)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
- jbNotificationExtras(lowpriority, nbuilder);
+
+ jbNotificationExtras(priority, nbuilder);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
lpNotificationExtras(nbuilder);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ //noinspection NewApi
+ nbuilder.setChannelId(channel);
+ if (mProfile != null)
+ //noinspection NewApi
+ nbuilder.setShortcutId(mProfile.getUUIDString());
+
+ }
+
if (tickerText != null && !tickerText.equals(""))
nbuilder.setTicker(tickerText);
@SuppressWarnings("deprecation")
Notification notification = nbuilder.getNotification();
+ int notificationId = channel.hashCode();
- mNotificationManager.notify(OPENVPN_STATUS, notification);
- // startForeground(OPENVPN_STATUS, notification);
+ mNotificationManager.notify(notificationId, notification);
+ startForeground(notificationId, notification);
+
+ if (lastChannel != null && !channel.equals(lastChannel)) {
+ // Cancel old notification
+ mNotificationManager.cancel(lastChannel.hashCode());
+ }
// Check if running on a TV
- if (runningOnAndroidTV() && !lowpriority)
+ if (runningOnAndroidTV() && !(priority < 0))
guiHandler.post(new Runnable() {
@Override
@@ -238,13 +310,12 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
- private void jbNotificationExtras(boolean lowpriority,
+ private void jbNotificationExtras(int priority,
android.app.Notification.Builder nbuilder) {
try {
- if (lowpriority) {
+ if (priority != 0) {
Method setpriority = nbuilder.getClass().getMethod("setPriority", int.class);
- // PRIORITY_MIN == -2
- setpriority.invoke(nbuilder, -2);
+ setpriority.invoke(nbuilder, priority);
Method setUsesChronometer = nbuilder.getClass().getMethod("setUsesChronometer", boolean.class);
setUsesChronometer.invoke(nbuilder, true);
@@ -281,9 +352,20 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
}
- PendingIntent getLogPendingIntent() {
+ PendingIntent getUserInputIntent(String needed) {
+ Intent intent = new Intent(getApplicationContext(), LaunchVPN.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+ intent.putExtra("need", needed);
+ Bundle b = new Bundle();
+ b.putString("need", needed);
+ PendingIntent pIntent = PendingIntent.getActivity(this, 12, intent, 0);
+ return pIntent;
+ }
+
+ PendingIntent getGraphPendingIntent() {
// Let the configure Button show the Log
Intent intent = new Intent(getBaseContext(), Dashboard.class);
+ intent.putExtra("PAGE", "graph");
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
PendingIntent startLW = PendingIntent.getActivity(this, 0, intent, 0);
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
@@ -298,6 +380,10 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
mDeviceStateReceiver = new DeviceStateReceiver(magnagement);
+
+ // Fetch initial network state
+ mDeviceStateReceiver.networkStateChange(this);
+
registerReceiver(mDeviceStateReceiver, filter);
VpnStatus.addByteCountListener(mDeviceStateReceiver);
@@ -329,6 +415,14 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
}
@Override
+ public boolean stopVPN(boolean replaceConnection) throws RemoteException {
+ if (getManagement() != null)
+ return getManagement().stopVPN(replaceConnection);
+ else
+ return false;
+ }
+
+ @Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null && intent.getBooleanExtra(ALWAYS_SHOW_NOTIFICATION, false))
@@ -359,26 +453,32 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
return START_REDELIVER_INTENT;
}
- /* The intent is null when the service has been restarted */
- if (intent == null) {
- mProfile = ProfileManager.getLastConnectedProfile(this, false);
+ if (intent != null && intent.hasExtra(getPackageName() + ".profileUUID")) {
+ String profileUUID = intent.getStringExtra(getPackageName() + ".profileUUID");
+ int profileVersion = intent.getIntExtra(getPackageName() + ".profileVersion", 0);
+ // 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) {
+ updateShortCutUsage(mProfile);
+ }
+
+ } else {
+ /* The intent is null when we are set as always-on or the service has been restarted. */
+ mProfile = ProfileManager.getLastConnectedProfile(this);
VpnStatus.logInfo(R.string.service_restarted);
/* Got no profile, just stop */
if (mProfile == null) {
- Log.d("OpenVPN", "Got no last connected profile on null intent. Stopping");
- stopSelf(startId);
- return START_NOT_STICKY;
+ Log.d("OpenVPN", "Got no last connected profile on null intent. Assuming always on.");
+ mProfile = ProfileManager.getAlwaysOnVPN(this);
+
+ if (mProfile == null) {
+ stopSelf(startId);
+ return START_NOT_STICKY;
+ }
}
/* Do the asynchronous keychain certificate stuff */
mProfile.checkForRestart(this);
-
- /* Recreate the intent */
- intent = mProfile.getStartServiceIntent(this);
-
- } else {
- String profileUUID = intent.getStringExtra(getPackageName() + ".profileUUID");
- mProfile = ProfileManager.get(this, profileUUID);
}
/* start the OpenVPN process itself in a background thread */
@@ -391,17 +491,22 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
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;
+ VpnStatus.setConnectedVPNProfile(mProfile.getUUIDString());
return START_STICKY;
}
+ @RequiresApi(Build.VERSION_CODES.N_MR1)
+ private void updateShortCutUsage(VpnProfile profile) {
+ if (profile == null)
+ return;
+ ShortcutManager shortcutManager = getSystemService(ShortcutManager.class);
+ shortcutManager.reportShortcutUsed(profile.getUUIDString());
+ }
+
private void startOpenVPN() {
VpnStatus.logInfo(R.string.building_configration);
- VpnStatus.updateStateString("VPN_GENERATE_CONFIG", "", R.string.building_configration, VpnStatus.ConnectionStatus.LEVEL_START);
+ VpnStatus.updateStateString("VPN_GENERATE_CONFIG", "", R.string.building_configration, ConnectionStatus.LEVEL_START);
try {
@@ -411,12 +516,9 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
endVpnService();
return;
}
-
- // Extract information from the intent.
- String prefix = getPackageName();
String nativeLibraryDirectory = getApplicationInfo().nativeLibraryDir;
- // Also writes OpenVPN binary
+ // Write OpenVPN binary
String[] argv = VPNLaunchHelper.buildOpenvpnArgv(this);
@@ -429,7 +531,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
mStarting = false;
// Start a new session by creating a new thread.
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ SharedPreferences prefs = Preferences.getDefaultSharedPreferences(this);
mOvpn3 = prefs.getBoolean("ovpn3", false);
if (!"ovpn3".equals(BuildConfig.FLAVOR))
@@ -462,8 +564,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
} else {
- HashMap<String, String> env = new HashMap<>();
- processThread = new OpenVPNThread(this, argv, env, nativeLibraryDirectory);
+ processThread = new OpenVPNThread(this, argv, nativeLibraryDirectory);
mOpenVPNThread = processThread;
}
@@ -475,21 +576,21 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
}
new Handler(getMainLooper()).post(new Runnable() {
- @Override
- public void run() {
- if (mDeviceStateReceiver != null)
- unregisterDeviceStateReceiver();
+ @Override
+ public void run() {
+ if (mDeviceStateReceiver != null)
+ unregisterDeviceStateReceiver();
- registerDeviceStateReceiver(mManagement);
- }
- }
+ registerDeviceStateReceiver(mManagement);
+ }
+ }
- );
+ );
}
private void stopOldOpenVPNProcess() {
if (mManagement != null) {
- if (mOpenVPNThread!=null)
+ if (mOpenVPNThread != null)
((OpenVPNThread) mOpenVPNThread).setReplaceConnection();
if (mManagement.stopVPN(true)) {
// an old was asked to exit, wait 1s
@@ -501,6 +602,10 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
}
}
+ forceStopOpenVpnProcess();
+ }
+
+ public void forceStopOpenVpnProcess() {
synchronized (mProcessLock) {
if (mProcessThread != null) {
mProcessThread.interrupt();
@@ -525,6 +630,16 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
}
@Override
+ public IBinder asBinder() {
+ return mBinder;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ }
+
+ @Override
public void onDestroy() {
synchronized (mProcessLock) {
if (mProcessThread != null) {
@@ -538,7 +653,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
// Just in case unregister for state
VpnStatus.removeStateListener(this);
VpnStatus.flushLog();
-
}
private String getTunConfigString() {
@@ -636,7 +750,9 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
positiveIPv4Routes.add(dnsServer);
}
} catch (Exception e) {
- VpnStatus.logError("Error parsing DNS Server IP: " + mDnslist.get(0));
+ // If it looks like IPv6 ignore error
+ if (!mDnslist.get(0).contains(":"))
+ VpnStatus.logError("Error parsing DNS Server IP: " + mDnslist.get(0));
}
}
@@ -698,7 +814,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
mLocalIPv6 = null;
mDomain = null;
- builder.setConfigureIntent(getLogPendingIntent());
+ builder.setConfigureIntent(getGraphPendingIntent());
try {
//Debug.stopMethodTracing();
@@ -812,7 +928,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
NetworkSpace.ipAddress gatewayIP = new NetworkSpace.ipAddress(new CIDRIP(gateway, 32), false);
if (mLocalIP == null) {
- VpnStatus.logError("Local IP address unset but adding route?! This is broken! Please contact author with log");
+ 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;
}
NetworkSpace.ipAddress localNet = new NetworkSpace.ipAddress(mLocalIP, true);
@@ -924,7 +1040,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
if (mProcessThread == null && !mNotificationAlwaysVisible)
return;
- boolean lowpriority = false;
+ String channel = NOTIFICATION_CHANNEL_NEWSTATUS_ID;
// Display byte count only after being connected
{
@@ -936,11 +1052,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
mDisplayBytecount = true;
mConnecttime = System.currentTimeMillis();
if (!runningOnAndroidTV())
- lowpriority = true;
-
- String ns = Context.NOTIFICATION_SERVICE;
- NotificationManager mNotificationManager = (NotificationManager) getSystemService(ns);
- mNotificationManager.cancel(OPENVPN_STATUS);
+ channel = NOTIFICATION_CHANNEL_BG_ID;
} else {
mDisplayBytecount = false;
}
@@ -949,13 +1061,16 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
// This also mean we are no longer connected, ignore bytecount messages until next
// CONNECTED
// Does not work :(
- String msg = getString(resid);
- // showNotification(VpnStatus.getLastCleanLogMessage(this),
- // msg, lowpriority, 0, level);
+ showNotification(VpnStatus.getLastCleanLogMessage(this),
+ VpnStatus.getLastCleanLogMessage(this), channel, 0, level);
}
}
+ @Override
+ public void setConnectedVPN(String uuid) {
+ }
+
private void doSendBroadcast(String state, ConnectionStatus level) {
Intent vpnstatus = new Intent();
vpnstatus.setAction("de.blinkt.openvpn.VPN_STATUS");
@@ -968,13 +1083,13 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
public void updateByteCount(long in, long out, long diffIn, long diffOut) {
if (mDisplayBytecount) {
String netstat = String.format(getString(R.string.statusline_bytecount),
- humanReadableByteCount(in, false),
- humanReadableByteCount(diffIn / OpenVPNManagement.mBytecountInterval, true),
- humanReadableByteCount(out, false),
- humanReadableByteCount(diffOut / OpenVPNManagement.mBytecountInterval, true));
+ humanReadableByteCount(in, false, getResources()),
+ humanReadableByteCount(diffIn / OpenVPNManagement.mBytecountInterval, true, getResources()),
+ humanReadableByteCount(out, false, getResources()),
+ humanReadableByteCount(diffOut / OpenVPNManagement.mBytecountInterval, true, getResources()));
- boolean lowpriority = !mNotificationAlwaysVisible;
- //showNotification(netstat, null, lowpriority, mConnecttime, LEVEL_CONNECTED);
+
+ showNotification(netstat, null, NOTIFICATION_CHANNEL_BG_ID, mConnecttime, LEVEL_CONNECTED);
}
}
@@ -1009,10 +1124,8 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
}
}
- public class LocalBinder extends Binder {
- public OpenVPNService getService() {
- // Return this instance of LocalService so clients can call public methods
- return OpenVPNService.this;
- }
+ public void requestInputFromUser(int resid, String needed) {
+ VpnStatus.updateStateString("NEED", "need " + needed, resid, LEVEL_WAITING_FOR_USER_INPUT);
+ showNotification(getString(resid), getString(resid), NOTIFICATION_CHANNEL_NEWSTATUS_ID, 0, LEVEL_WAITING_FOR_USER_INPUT);
}
}
diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNStatusService.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNStatusService.java
new file mode 100644
index 00000000..6df1379a
--- /dev/null
+++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNStatusService.java
@@ -0,0 +1,232 @@
+/*
+ * 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.app.Service;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.support.annotation.Nullable;
+import android.util.Pair;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+
+/**
+ * Created by arne on 08.11.16.
+ */
+
+public class OpenVPNStatusService extends Service implements VpnStatus.LogListener, VpnStatus.ByteCountListener, VpnStatus.StateListener {
+ @Nullable
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+
+ static final RemoteCallbackList<IStatusCallbacks> mCallbacks =
+ new RemoteCallbackList<>();
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ VpnStatus.addLogListener(this);
+ VpnStatus.addByteCountListener(this);
+ VpnStatus.addStateListener(this);
+ mHandler.setService(this);
+
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+
+ VpnStatus.removeLogListener(this);
+ VpnStatus.removeByteCountListener(this);
+ VpnStatus.removeStateListener(this);
+ mCallbacks.kill();
+
+ }
+
+ private static final IServiceStatus.Stub mBinder = new IServiceStatus.Stub() {
+
+ @Override
+ public ParcelFileDescriptor registerStatusCallback(IStatusCallbacks cb) throws RemoteException {
+ final LogItem[] logbuffer = VpnStatus.getlogbuffer();
+ if (mLastUpdateMessage != null)
+ sendUpdate(cb, mLastUpdateMessage);
+
+ mCallbacks.register(cb);
+ try {
+ final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+ new Thread("pushLogs") {
+ @Override
+ public void run() {
+ DataOutputStream fd = new DataOutputStream(new ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]));
+ try {
+ synchronized (VpnStatus.readFileLock) {
+ if (!VpnStatus.readFileLog) {
+ VpnStatus.readFileLock.wait();
+ }
+ }
+ } catch (InterruptedException e) {
+ VpnStatus.logException(e);
+ }
+ try {
+
+ for (LogItem logItem : logbuffer) {
+ byte[] bytes = logItem.getMarschaledBytes();
+ fd.writeShort(bytes.length);
+ fd.write(bytes);
+ }
+ // Mark end
+ fd.writeShort(0x7fff);
+ fd.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ }
+ }.start();
+ return pipe[0];
+ } catch (IOException e) {
+ e.printStackTrace();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
+ throw new RemoteException(e.getMessage());
+ }
+ return null;
+ }
+ }
+
+ @Override
+ public void unregisterStatusCallback(IStatusCallbacks cb) throws RemoteException {
+ mCallbacks.unregister(cb);
+ }
+
+ @Override
+ public String getLastConnectedVPN() throws RemoteException {
+ return VpnStatus.getLastConnectedVPNProfile();
+ }
+
+ @Override
+ public void setCachedPassword(String uuid, int type, String password) {
+ PasswordCache.setCachedPassword(uuid, type, password);
+ }
+
+ @Override
+ public TrafficHistory getTrafficHistory() throws RemoteException {
+ return VpnStatus.trafficHistory;
+ }
+
+ };
+
+ @Override
+ public void newLog(LogItem logItem) {
+ Message msg = mHandler.obtainMessage(SEND_NEW_LOGITEM, logItem);
+ msg.sendToTarget();
+ }
+
+ @Override
+ public void updateByteCount(long in, long out, long diffIn, long diffOut) {
+ Message msg = mHandler.obtainMessage(SEND_NEW_BYTECOUNT, Pair.create(in, out));
+ msg.sendToTarget();
+ }
+
+ static UpdateMessage mLastUpdateMessage;
+
+ static class UpdateMessage {
+ public String state;
+ public String logmessage;
+ public ConnectionStatus level;
+ int resId;
+
+ UpdateMessage(String state, String logmessage, int resId, ConnectionStatus level) {
+ this.state = state;
+ this.resId = resId;
+ this.logmessage = logmessage;
+ this.level = level;
+ }
+ }
+
+
+ @Override
+ public void updateState(String state, String logmessage, int localizedResId, ConnectionStatus level) {
+
+ mLastUpdateMessage = new UpdateMessage(state, logmessage, localizedResId, level);
+ Message msg = mHandler.obtainMessage(SEND_NEW_STATE, mLastUpdateMessage);
+ msg.sendToTarget();
+ }
+
+ @Override
+ public void setConnectedVPN(String uuid) {
+ Message msg = mHandler.obtainMessage(SEND_NEW_CONNECTED_VPN, uuid);
+ msg.sendToTarget();
+ }
+
+ private static final OpenVPNStatusHandler mHandler = new OpenVPNStatusHandler();
+
+ private static final int SEND_NEW_LOGITEM = 100;
+ private static final int SEND_NEW_STATE = 101;
+ private static final int SEND_NEW_BYTECOUNT = 102;
+ private static final int SEND_NEW_CONNECTED_VPN = 103;
+
+ private static class OpenVPNStatusHandler extends Handler {
+ WeakReference<OpenVPNStatusService> service = null;
+
+ private void setService(OpenVPNStatusService statusService) {
+ service = new WeakReference<>(statusService);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+
+ RemoteCallbackList<IStatusCallbacks> callbacks;
+ if (service == null || service.get() == null)
+ return;
+ callbacks = service.get().mCallbacks;
+ // Broadcast to all clients the new value.
+ final int N = callbacks.beginBroadcast();
+ for (int i = 0; i < N; i++) {
+
+ try {
+ IStatusCallbacks broadcastItem = callbacks.getBroadcastItem(i);
+
+ switch (msg.what) {
+ case SEND_NEW_LOGITEM:
+ broadcastItem.newLogItem((LogItem) msg.obj);
+ break;
+ case SEND_NEW_BYTECOUNT:
+ Pair<Long, Long> inout = (Pair<Long, Long>) msg.obj;
+ broadcastItem.updateByteCount(inout.first, inout.second);
+ break;
+ case SEND_NEW_STATE:
+ sendUpdate(broadcastItem, (UpdateMessage) msg.obj);
+ break;
+
+ case SEND_NEW_CONNECTED_VPN:
+ broadcastItem.connectedVPN((String) msg.obj);
+ break;
+ }
+ } catch (RemoteException e) {
+ // The RemoteCallbackList will take care of removing
+ // the dead object for us.
+ }
+ }
+ callbacks.finishBroadcast();
+ }
+ }
+
+ private static void sendUpdate(IStatusCallbacks broadcastItem,
+ UpdateMessage um) throws RemoteException {
+ broadcastItem.updateStateString(um.state, um.logmessage, um.resId, um.level);
+ }
+} \ No newline at end of file
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 e0c39546..c96f88c4 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java
@@ -19,15 +19,10 @@ import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.Locale;
-import java.util.Map;
-import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import se.leap.bitmaskclient.R;
-import de.blinkt.openvpn.VpnProfile;
-import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus;
-import de.blinkt.openvpn.core.VpnStatus.LogItem;
public class OpenVPNThread implements Runnable {
private static final String DUMP_PATH_STRING = "Dump path: ";
@@ -44,15 +39,13 @@ public class OpenVPNThread implements Runnable {
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) {
+ public OpenVPNThread(OpenVPNService service, String[] argv, String nativelibdir) {
mArgv = argv;
mNativeDir = nativelibdir;
mService = service;
- mProcessEnv = processEnv;
}
public void stopProcess() {
@@ -68,7 +61,7 @@ public class OpenVPNThread implements Runnable {
public void run() {
try {
Log.i(TAG, "Starting openvpn");
- startOpenVPNThreadArgs(mArgv, mProcessEnv);
+ startOpenVPNThreadArgs(mArgv);
Log.i(TAG, "OpenVPN process exited");
} catch (Exception e) {
VpnStatus.logException("Starting OpenVPN Thread", e);
@@ -94,7 +87,6 @@ public class OpenVPNThread implements Runnable {
mArgv = noPieArgv;
VpnStatus.logInfo("PIE Version could not be executed. Trying no PIE version");
run();
- return;
}
}
@@ -124,7 +116,7 @@ public class OpenVPNThread implements Runnable {
}
}
- private void startOpenVPNThreadArgs(String[] argv, Map<String, String> env) {
+ private void startOpenVPNThreadArgs(String[] argv) {
LinkedList<String> argvlist = new LinkedList<String>();
Collections.addAll(argvlist, argv);
@@ -136,10 +128,6 @@ public class OpenVPNThread implements Runnable {
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();
@@ -164,6 +152,7 @@ public class OpenVPNThread implements Runnable {
Pattern p = Pattern.compile("(\\d+).(\\d+) ([0-9a-f])+ (.*)");
Matcher m = p.matcher(logline);
+ int logerror = 0;
if (m.matches()) {
int flags = Integer.parseInt(m.group(3), 16);
String msg = m.group(4);
@@ -183,15 +172,22 @@ public class OpenVPNThread implements Runnable {
if (msg.startsWith("MANAGEMENT: CMD"))
logLevel = Math.max(4, logLevel);
+ if ((msg.endsWith("md too weak") && msg.startsWith("OpenSSL: error")) || msg.contains("error:140AB18E"))
+ logerror = 1;
VpnStatus.logMessageOpenVPN(logStatus, logLevel, msg);
+ if (logerror==1)
+ VpnStatus.logError("OpenSSL reproted a certificate with a weak hash, please the in app FAQ about weak hashes");
+
} else {
VpnStatus.logInfo("P:" + logline);
}
- }
-
- } catch (IOException e) {
+ if (Thread.interrupted()) {
+ throw new InterruptedException("OpenVpn process was killed form java code");
+ }
+ }
+ } catch (InterruptedException | IOException e) {
VpnStatus.logException("Error reading from output of OpenVPN process", e);
stopProcess();
}
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 569a3846..492e8913 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java
@@ -9,8 +9,10 @@ import android.content.Context;
import android.net.LocalServerSocket;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
+import android.os.Handler;
import android.os.ParcelFileDescriptor;
import android.support.annotation.NonNull;
+import android.text.TextUtils;
import android.util.Log;
import junit.framework.Assert;
@@ -31,11 +33,11 @@ import java.util.Vector;
import se.leap.bitmaskclient.BuildConfig;
import se.leap.bitmaskclient.R;
import de.blinkt.openvpn.VpnProfile;
-import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus;
public class OpenVpnManagementThread implements Runnable, OpenVPNManagement {
private static final String TAG = "openvpn";
+ private final Handler mResumeHandler;
private LocalSocket mSocket;
private VpnProfile mProfile;
private OpenVPNService mOpenVPNService;
@@ -54,8 +56,19 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement {
public OpenVpnManagementThread(VpnProfile profile, OpenVPNService openVpnService) {
mProfile = profile;
mOpenVPNService = openVpnService;
+ mResumeHandler = new Handler(openVpnService.getMainLooper());
+
}
+ private Runnable mResumeHoldRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (shouldBeRunning()) {
+ releaseHoldCmd();
+ }
+ }
+ };
+
public boolean openManagementInterface(@NonNull Context c) {
// Could take a while to open connection
int tries = 8;
@@ -92,15 +105,21 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement {
}
- public void managmentCommand(String cmd) {
+ /**
+ * @param cmd command to write to management socket
+ * @return true if command have been sent
+ */
+ public boolean managmentCommand(String cmd) {
try {
if (mSocket != null && mSocket.getOutputStream() != null) {
mSocket.getOutputStream().write(cmd.getBytes());
mSocket.getOutputStream().flush();
+ return true;
}
} catch (IOException e) {
// Ignore socket stack traces
}
+ return false;
}
@@ -118,13 +137,20 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement {
// Wait for a client to connect
mSocket = mServerSocket.accept();
InputStream instream = mSocket.getInputStream();
+
+
// Close the management socket after client connected
+ try {
+ mServerSocket.close();
+ } catch (IOException e) {
+ VpnStatus.logException(e);
+ }
- mServerSocket.close();
// Closing one of the two sockets also closes the other
//mServerSocketLocal.close();
while (true) {
+
int numbytesread = instream.read(buffer);
if (numbytesread == -1)
return;
@@ -215,7 +241,7 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement {
processPWCommand(argument);
break;
case "HOLD":
- handleHold();
+ handleHold(argument);
break;
case "NEED-OK":
processNeedCommand(argument);
@@ -305,19 +331,26 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement {
return mPauseCallback.shouldBeRunning();
}
- private void handleHold() {
+ private void handleHold(String argument) {
+ mWaitingForRelease = true;
+ int waittime = Integer.parseInt(argument.split(":")[1]);
if (shouldBeRunning()) {
- releaseHoldCmd();
- } else {
- mWaitingForRelease = true;
+ 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);
+ if (waittime > 5)
+ VpnStatus.logInfo(R.string.state_waitconnectretry, String.valueOf(waittime));
+ else
+ VpnStatus.logDebug(R.string.state_waitconnectretry, String.valueOf(waittime));
+ } else {
VpnStatus.updateStatePause(lastPauseReason);
-
-
}
}
private void releaseHoldCmd() {
+ mResumeHandler.removeCallbacks(mResumeHoldRunnable);
if ((System.currentTimeMillis() - mLastHoldRelease) < 5000) {
try {
Thread.sleep(3000);
@@ -402,6 +435,7 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement {
protectFileDescriptor(fdtoprotect);
break;
case "DNSSERVER":
+ case "DNS6SERVER":
mOpenVPNService.addDNS(extra);
break;
case "DNSDOMAIN":
@@ -529,15 +563,17 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement {
if (needed.equals("Private Key")) {
pw = mProfile.getPasswordPrivateKey();
} else if (needed.equals("Auth")) {
+ pw = mProfile.getPasswordAuth();
+
String usercmd = String.format("username '%s' %s\n",
needed, VpnProfile.openVpnEscape(mProfile.mUsername));
managmentCommand(usercmd);
- pw = mProfile.getPasswordAuth();
}
if (pw != null) {
String cmd = String.format("password '%s' %s\n", needed, VpnProfile.openVpnEscape(pw));
managmentCommand(cmd);
} else {
+ mOpenVPNService.requestInputFromUser(R.string.password, needed);
VpnStatus.logError(String.format("Openvpn requires Authentication type '%s' but no password/key information available", needed));
}
@@ -553,8 +589,7 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement {
synchronized (active) {
boolean sendCMD = false;
for (OpenVpnManagementThread mt : active) {
- mt.managmentCommand("signal SIGINT\n");
- sendCMD = true;
+ sendCMD = mt.managmentCommand("signal SIGINT\n");
try {
if (mt.mSocket != null)
mt.mSocket.close();
@@ -571,7 +606,7 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement {
if (mWaitingForRelease)
releaseHold();
else if (samenetwork)
- managmentCommand("network-change samenetwork\n");
+ managmentCommand("network-change\n");
else
managmentCommand("network-change\n");
}
@@ -582,6 +617,7 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement {
}
public void signalusr1() {
+ mResumeHandler.removeCallbacks(mResumeHoldRunnable);
if (!mWaitingForRelease)
managmentCommand("signal SIGUSR1\n");
else
@@ -624,8 +660,12 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement {
@Override
public boolean stopVPN(boolean replaceConnection) {
- mShuttingDown = true;
- return stopOpenVPN();
+ boolean stopSucceed = stopOpenVPN();
+ if (stopSucceed) {
+ mShuttingDown = true;
+
+ }
+ return stopSucceed;
}
}
diff --git a/app/src/main/java/de/blinkt/openvpn/core/PasswordCache.java b/app/src/main/java/de/blinkt/openvpn/core/PasswordCache.java
new file mode 100644
index 00000000..179a8a7b
--- /dev/null
+++ b/app/src/main/java/de/blinkt/openvpn/core/PasswordCache.java
@@ -0,0 +1,61 @@
+/*
+ * 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 java.util.UUID;
+
+/**
+ * Created by arne on 15.12.16.
+ */
+
+public class PasswordCache {
+ public static final int PCKS12ORCERTPASSWORD = 2;
+ public static final int AUTHPASSWORD = 3;
+ private static PasswordCache mInstance;
+ final private UUID mUuid;
+ private String mKeyOrPkcs12Password;
+ private String mAuthPassword;
+
+ private PasswordCache(UUID uuid) {
+ mUuid = uuid;
+ }
+
+ public static PasswordCache getInstance(UUID uuid) {
+ if (mInstance == null || !mInstance.mUuid.equals(uuid)) {
+ mInstance = new PasswordCache(uuid);
+ }
+ return mInstance;
+ }
+
+ public static String getPKCS12orCertificatePassword(UUID uuid, boolean resetPw) {
+ String pwcopy = getInstance(uuid).mKeyOrPkcs12Password;
+ if (resetPw)
+ getInstance(uuid).mKeyOrPkcs12Password = null;
+ return pwcopy;
+ }
+
+
+ public static String getAuthPassword(UUID uuid, boolean resetPW) {
+ String pwcopy = getInstance(uuid).mAuthPassword;
+ if (resetPW)
+ getInstance(uuid).mAuthPassword = null;
+ return pwcopy;
+ }
+
+ public static void setCachedPassword(String uuid, int type, String password) {
+ PasswordCache instance = getInstance(UUID.fromString(uuid));
+ switch (type) {
+ case PCKS12ORCERTPASSWORD:
+ instance.mKeyOrPkcs12Password = password;
+ break;
+ case AUTHPASSWORD:
+ instance.mAuthPassword = password;
+ break;
+ }
+ }
+
+
+}
diff --git a/app/src/main/java/de/blinkt/openvpn/core/Preferences.java b/app/src/main/java/de/blinkt/openvpn/core/Preferences.java
new file mode 100644
index 00000000..76a064ff
--- /dev/null
+++ b/app/src/main/java/de/blinkt/openvpn/core/Preferences.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+/**
+ * Created by arne on 08.01.17.
+ */
+
+// Until I find a good solution
+
+public class Preferences {
+
+ static SharedPreferences getSharedPreferencesMulti(String name, Context c) {
+ return c.getSharedPreferences(name, Context.MODE_MULTI_PROCESS | Context.MODE_PRIVATE);
+
+ }
+
+
+ public static SharedPreferences getDefaultSharedPreferences(Context c) {
+ return c.getSharedPreferences(c.getPackageName() + "_preferences", Context.MODE_MULTI_PROCESS | Context.MODE_PRIVATE);
+
+ }
+
+
+}
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 4f9c219b..f776fc2e 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/ProfileManager.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/ProfileManager.java
@@ -9,7 +9,6 @@ import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
-import android.preference.PreferenceManager;
import java.io.IOException;
import java.io.ObjectInputStream;
@@ -17,7 +16,9 @@ import java.io.ObjectOutputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Locale;
import java.util.Set;
+import java.util.UUID;
import de.blinkt.openvpn.VpnProfile;
@@ -25,6 +26,7 @@ public class ProfileManager {
private static final String PREFS_NAME = "VPNList";
private static final String LAST_CONNECTED_PROFILE = "lastConnectedProfile";
+ private static final String TEMPORARY_PROFILE_FILENAME = "temporary-vpn-profile";
private static ProfileManager instance;
private static VpnProfile mLastConnectedVpn = null;
@@ -59,30 +61,31 @@ public class ProfileManager {
}
public static void setConntectedVpnProfileDisconnected(Context c) {
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c);
+ SharedPreferences prefs = Preferences.getDefaultSharedPreferences(c);
Editor prefsedit = prefs.edit();
prefsedit.putString(LAST_CONNECTED_PROFILE, null);
prefsedit.apply();
}
- public static void setConnectedVpnProfile(Context c, VpnProfile connectedrofile) {
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c);
+ /**
+ * Sets the profile that is connected (to connect if the service restarts)
+ */
+ public static void setConnectedVpnProfile(Context c, VpnProfile connectedProfile) {
+ SharedPreferences prefs = Preferences.getDefaultSharedPreferences(c);
Editor prefsedit = prefs.edit();
- prefsedit.putString(LAST_CONNECTED_PROFILE, connectedrofile.getUUIDString());
+ prefsedit.putString(LAST_CONNECTED_PROFILE, connectedProfile.getUUIDString());
prefsedit.apply();
- mLastConnectedVpn = connectedrofile;
+ mLastConnectedVpn = connectedProfile;
}
- public static VpnProfile getLastConnectedProfile(Context c, boolean onBoot) {
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c);
-
- boolean useStartOnBoot = prefs.getBoolean("restartvpnonboot", false);
-
- if (onBoot && !useStartOnBoot)
- return null;
+ /**
+ * Returns the profile that was last connected (to connect if the service restarts)
+ */
+ public static VpnProfile getLastConnectedProfile(Context c) {
+ SharedPreferences prefs = Preferences.getDefaultSharedPreferences(c);
String lastConnectedProfile = prefs.getString(LAST_CONNECTED_PROFILE, null);
if (lastConnectedProfile != null)
@@ -106,7 +109,7 @@ public class ProfileManager {
}
public void saveProfileList(Context context) {
- SharedPreferences sharedprefs = context.getSharedPreferences(PREFS_NAME, Activity.MODE_PRIVATE);
+ SharedPreferences sharedprefs = Preferences.getSharedPreferencesMulti(PREFS_NAME, context);
Editor editor = sharedprefs.edit();
editor.putStringSet("vpnlist", profiles.keySet());
@@ -124,24 +127,35 @@ public class ProfileManager {
}
- public static void setTemporaryProfile(VpnProfile tmp) {
+ public static void setTemporaryProfile(Context c, VpnProfile tmp) {
ProfileManager.tmpprofile = tmp;
+ saveProfile(c, tmp, true, true);
}
- public static boolean isTempProfile()
- {
- return mLastConnectedVpn == tmpprofile;
+ public static boolean isTempProfile() {
+ return mLastConnectedVpn != null && mLastConnectedVpn == tmpprofile;
}
-
public void saveProfile(Context context, VpnProfile profile) {
- ObjectOutputStream vpnfile;
+ saveProfile(context, profile, true, false);
+ }
+
+ private static void saveProfile(Context context, VpnProfile profile, boolean updateVersion, boolean isTemporary) {
+
+ if (updateVersion)
+ profile.mVersion += 1;
+ ObjectOutputStream vpnFile;
+
+ String filename = profile.getUUID().toString() + ".vp";
+ if (isTemporary)
+ filename = TEMPORARY_PROFILE_FILENAME + ".vp";
+
try {
- vpnfile = new ObjectOutputStream(context.openFileOutput((profile.getUUID().toString() + ".vp"), Activity.MODE_PRIVATE));
+ vpnFile = new ObjectOutputStream(context.openFileOutput(filename, Activity.MODE_PRIVATE));
- vpnfile.writeObject(profile);
- vpnfile.flush();
- vpnfile.close();
+ vpnFile.writeObject(profile);
+ vpnFile.flush();
+ vpnFile.close();
} catch (IOException e) {
VpnStatus.logException("saving VPN profile", e);
throw new RuntimeException(e);
@@ -151,11 +165,13 @@ public class ProfileManager {
private void loadVPNList(Context context) {
profiles = new HashMap<>();
- SharedPreferences listpref = context.getSharedPreferences(PREFS_NAME, Activity.MODE_PRIVATE);
+ SharedPreferences listpref = Preferences.getSharedPreferencesMulti(PREFS_NAME, context);
Set<String> vlist = listpref.getStringSet("vpnlist", null);
if (vlist == null) {
vlist = new HashSet<>();
}
+ // Always try to load the temporary profile
+ vlist.add(TEMPORARY_PROFILE_FILENAME);
for (String vpnentry : vlist) {
try {
@@ -167,10 +183,15 @@ public class ProfileManager {
continue;
vp.upgradeProfile();
- profiles.put(vp.getUUID().toString(), vp);
+ if (vpnentry.equals(TEMPORARY_PROFILE_FILENAME)) {
+ tmpprofile = vp;
+ } else {
+ profiles.put(vp.getUUID().toString(), vp);
+ }
} catch (IOException | ClassNotFoundException e) {
- VpnStatus.logException("Loading VPN List", e);
+ if (!vpnentry.equals(TEMPORARY_PROFILE_FILENAME))
+ VpnStatus.logException("Loading VPN List", e);
}
}
}
@@ -187,12 +208,49 @@ public class ProfileManager {
}
public static VpnProfile get(Context context, String profileUUID) {
+ return get(context, profileUUID, 0, 10);
+ }
+
+ public static VpnProfile get(Context context, String profileUUID, int version, int tries) {
checkInstance(context);
- return get(profileUUID);
+ VpnProfile profile = get(profileUUID);
+ int tried = 0;
+ while ((profile == null || profile.mVersion < version) && (tried++ < tries)) {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ignored) {
+ }
+ instance.loadVPNList(context);
+ profile = get(profileUUID);
+ int ver = profile == null ? -1 : profile.mVersion;
+ }
+
+ if (tried > 5)
+
+ {
+ int ver = profile == null ? -1 : profile.mVersion;
+ VpnStatus.logError(String.format(Locale.US, "Used x %d tries to get current version (%d/%d) of the profile", tried, ver, version));
+ }
+ return profile;
}
public static VpnProfile getLastConnectedVpn() {
return mLastConnectedVpn;
}
+ public static VpnProfile getAlwaysOnVPN(Context context) {
+ checkInstance(context);
+ SharedPreferences prefs = Preferences.getDefaultSharedPreferences(context);
+
+ String uuid = prefs.getString("alwaysOnVpn", null);
+ return get(uuid);
+
+ }
+
+ public static void updateLRU(Context c, VpnProfile profile) {
+ profile.mLastUsed = System.currentTimeMillis();
+ // LRU does not change the profile, no need for the service to refresh
+ if (profile!=tmpprofile)
+ saveProfile(c, profile, false, false);
+ }
}
diff --git a/app/src/main/java/de/blinkt/openvpn/core/StatusListener.java b/app/src/main/java/de/blinkt/openvpn/core/StatusListener.java
new file mode 100644
index 00000000..5d0b7037
--- /dev/null
+++ b/app/src/main/java/de/blinkt/openvpn/core/StatusListener.java
@@ -0,0 +1,109 @@
+/*
+ * 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.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Created by arne on 09.11.16.
+ */
+
+public class StatusListener {
+ private File mCacheDir;
+ private ServiceConnection mConnection = new ServiceConnection() {
+
+
+ @Override
+ public void onServiceConnected(ComponentName className,
+ IBinder service) {
+ // We've bound to LocalService, cast the IBinder and get LocalService instance
+ IServiceStatus serviceStatus = IServiceStatus.Stub.asInterface(service);
+ try {
+ /* Check if this a local service ... */
+ if (service.queryLocalInterface("de.blinkt.openvpn.core.IServiceStatus") == null) {
+ // Not a local service
+ VpnStatus.setConnectedVPNProfile(serviceStatus.getLastConnectedVPN());
+ VpnStatus.setTrafficHistory(serviceStatus.getTrafficHistory());
+ ParcelFileDescriptor pfd = serviceStatus.registerStatusCallback(mCallback);
+ DataInputStream fd = new DataInputStream(new ParcelFileDescriptor.AutoCloseInputStream(pfd));
+
+ short len = fd.readShort();
+ byte[] buf = new byte[65336];
+ while (len != 0x7fff) {
+ fd.readFully(buf, 0, len);
+ LogItem logitem = new LogItem(buf, len);
+ VpnStatus.newLogItem(logitem, false);
+ len = fd.readShort();
+ }
+ fd.close();
+
+
+
+ } else {
+ VpnStatus.initLogCache(mCacheDir);
+ }
+
+ } catch (RemoteException | IOException e) {
+ e.printStackTrace();
+ VpnStatus.logException(e);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName arg0) {
+
+ }
+
+ };
+
+ void init(Context c) {
+
+ Intent intent = new Intent(c, OpenVPNStatusService.class);
+ intent.setAction(OpenVPNService.START_SERVICE);
+ mCacheDir = c.getCacheDir();
+
+ c.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+
+
+ }
+
+
+ private IStatusCallbacks mCallback = new IStatusCallbacks.Stub()
+
+ {
+ @Override
+ public void newLogItem(LogItem item) throws RemoteException {
+ VpnStatus.newLogItem(item);
+ }
+
+ @Override
+ public void updateStateString(String state, String msg, int resid, ConnectionStatus
+ level) throws RemoteException {
+ VpnStatus.updateStateString(state, msg, resid, level);
+ }
+
+ @Override
+ public void updateByteCount(long inBytes, long outBytes) throws RemoteException {
+ VpnStatus.updateByteCount(inBytes, outBytes);
+ }
+
+ @Override
+ public void connectedVPN(String uuid) throws RemoteException {
+ VpnStatus.setConnectedVPNProfile(uuid);
+ }
+ };
+
+}
diff --git a/app/src/main/java/de/blinkt/openvpn/core/TrafficHistory.java b/app/src/main/java/de/blinkt/openvpn/core/TrafficHistory.java
new file mode 100644
index 00000000..6ba35066
--- /dev/null
+++ b/app/src/main/java/de/blinkt/openvpn/core/TrafficHistory.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (c) 2012-2017 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.Parcel;
+import android.os.Parcelable;
+
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Vector;
+
+import static java.lang.Math.max;
+
+/**
+ * Created by arne on 23.05.17.
+ */
+
+public class TrafficHistory implements Parcelable {
+
+ public static final long PERIODS_TO_KEEP = 5;
+ public static final int TIME_PERIOD_MINTUES = 60 * 1000;
+ public static final int TIME_PERIOD_HOURS = 3600 * 1000;
+ private LinkedList<TrafficDatapoint> trafficHistorySeconds = new LinkedList<>();
+ private LinkedList<TrafficDatapoint> trafficHistoryMinutes = new LinkedList<>();
+ private LinkedList<TrafficDatapoint> trafficHistoryHours = new LinkedList<>();
+
+ private TrafficDatapoint lastSecondUsedForMinute;
+ private TrafficDatapoint lastMinuteUsedForHours;
+
+ public TrafficHistory() {
+
+ }
+
+ protected TrafficHistory(Parcel in) {
+ in.readList(trafficHistorySeconds, getClass().getClassLoader());
+ in.readList(trafficHistoryMinutes, getClass().getClassLoader());
+ in.readList(trafficHistoryHours, getClass().getClassLoader());
+ lastSecondUsedForMinute = in.readParcelable(getClass().getClassLoader());
+ lastMinuteUsedForHours = in.readParcelable(getClass().getClassLoader());
+ }
+
+ public static final Creator<TrafficHistory> CREATOR = new Creator<TrafficHistory>() {
+ @Override
+ public TrafficHistory createFromParcel(Parcel in) {
+ return new TrafficHistory(in);
+ }
+
+ @Override
+ public TrafficHistory[] newArray(int size) {
+ return new TrafficHistory[size];
+ }
+ };
+
+ public LastDiff getLastDiff(TrafficDatapoint tdp) {
+
+ TrafficDatapoint lasttdp;
+
+
+ if (trafficHistorySeconds.size() == 0)
+ lasttdp = new TrafficDatapoint(0, 0, System.currentTimeMillis());
+
+ else
+ lasttdp = trafficHistorySeconds.getLast();
+
+ if (tdp == null) {
+ tdp = lasttdp;
+ if (trafficHistorySeconds.size() < 2)
+ lasttdp = tdp;
+ else {
+ trafficHistorySeconds.descendingIterator().next();
+ tdp = trafficHistorySeconds.descendingIterator().next();
+ }
+ }
+
+ return new LastDiff(lasttdp, tdp);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeList(trafficHistorySeconds);
+ dest.writeList(trafficHistoryMinutes);
+ dest.writeList(trafficHistoryHours);
+ dest.writeParcelable(lastSecondUsedForMinute, 0);
+ dest.writeParcelable(lastMinuteUsedForHours, 0);
+
+ }
+
+ public LinkedList<TrafficDatapoint> getHours() {
+ return trafficHistoryHours;
+ }
+
+ public LinkedList<TrafficDatapoint> getMinutes() {
+ return trafficHistoryMinutes;
+ }
+
+ public LinkedList<TrafficDatapoint> getSeconds() {
+ return trafficHistorySeconds;
+ }
+
+ public static LinkedList<TrafficDatapoint> getDummyList() {
+ LinkedList<TrafficDatapoint> list = new LinkedList<>();
+ list.add(new TrafficDatapoint(0, 0, System.currentTimeMillis()));
+ return list;
+ }
+
+
+ public static class TrafficDatapoint implements Parcelable {
+ private TrafficDatapoint(long inBytes, long outBytes, long timestamp) {
+ this.in = inBytes;
+ this.out = outBytes;
+ this.timestamp = timestamp;
+ }
+
+ public final long timestamp;
+ public final long in;
+ public final long out;
+
+ private TrafficDatapoint(Parcel in) {
+ timestamp = in.readLong();
+ this.in = in.readLong();
+ out = in.readLong();
+ }
+
+ public static final Creator<TrafficDatapoint> CREATOR = new Creator<TrafficDatapoint>() {
+ @Override
+ public TrafficDatapoint createFromParcel(Parcel in) {
+ return new TrafficDatapoint(in);
+ }
+
+ @Override
+ public TrafficDatapoint[] newArray(int size) {
+ return new TrafficDatapoint[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(timestamp);
+ dest.writeLong(in);
+ dest.writeLong(out);
+ }
+ }
+
+ LastDiff add(long in, long out) {
+ TrafficDatapoint tdp = new TrafficDatapoint(in, out, System.currentTimeMillis());
+
+ LastDiff diff = getLastDiff(tdp);
+ addDataPoint(tdp);
+ return diff;
+ }
+
+ private void addDataPoint(TrafficDatapoint tdp) {
+ trafficHistorySeconds.add(tdp);
+
+ if (lastSecondUsedForMinute == null) {
+ lastSecondUsedForMinute = new TrafficDatapoint(0, 0, 0);
+ lastMinuteUsedForHours = new TrafficDatapoint(0, 0, 0);
+ }
+
+ removeAndAverage(tdp, true);
+ }
+
+ private void removeAndAverage(TrafficDatapoint newTdp, boolean seconds) {
+ HashSet<TrafficDatapoint> toRemove = new HashSet<>();
+ Vector<TrafficDatapoint> toAverage = new Vector<>();
+
+ long timePeriod;
+ LinkedList<TrafficDatapoint> tpList, nextList;
+ TrafficDatapoint lastTsPeriod;
+
+ if (seconds) {
+ timePeriod = TIME_PERIOD_MINTUES;
+ tpList = trafficHistorySeconds;
+ nextList = trafficHistoryMinutes;
+ lastTsPeriod = lastSecondUsedForMinute;
+ } else {
+ timePeriod = TIME_PERIOD_HOURS;
+ tpList = trafficHistoryMinutes;
+ nextList = trafficHistoryHours;
+ lastTsPeriod = lastMinuteUsedForHours;
+ }
+
+ if (newTdp.timestamp / timePeriod > (lastTsPeriod.timestamp / timePeriod)) {
+ nextList.add(newTdp);
+
+ if (seconds) {
+ lastSecondUsedForMinute = newTdp;
+ removeAndAverage(newTdp, false);
+ } else
+ lastMinuteUsedForHours = newTdp;
+
+ for (TrafficDatapoint tph : tpList) {
+ // List is iteratered from oldest to newest, remembert first one that we did not
+ if ((newTdp.timestamp - tph.timestamp) / timePeriod >= PERIODS_TO_KEEP)
+ toRemove.add(tph);
+ }
+ tpList.removeAll(toRemove);
+ }
+ }
+
+ static class LastDiff {
+
+ final private TrafficDatapoint tdp;
+ final private TrafficDatapoint lasttdp;
+
+ private LastDiff(TrafficDatapoint lasttdp, TrafficDatapoint tdp) {
+ this.lasttdp = lasttdp;
+ this.tdp = tdp;
+ }
+
+ public long getDiffOut() {
+ return max(0, tdp.out - lasttdp.out);
+ }
+
+ public long getDiffIn() {
+ return max(0, tdp.in - lasttdp.in);
+ }
+
+ public long getIn() {
+ return tdp.in;
+ }
+
+ public long getOut() {
+ return tdp.out;
+ }
+
+ }
+
+
+} \ No newline at end of file
diff --git a/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java b/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java
index 78f462e7..f3b40381 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java
@@ -6,6 +6,7 @@
package de.blinkt.openvpn.core;
import android.annotation.TargetApi;
+import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
@@ -26,7 +27,6 @@ public class VPNLaunchHelper {
private static final String OVPNCONFIGFILE = "android.conf";
-
private static String writeMiniVPN(Context context) {
String[] abis;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
@@ -38,36 +38,34 @@ public class VPNLaunchHelper {
String nativeAPI = NativeUtils.getNativeAPI();
if (!nativeAPI.equals(abis[0])) {
VpnStatus.logWarning(R.string.abi_mismatch, Arrays.toString(abis), nativeAPI);
- abis = new String[] {nativeAPI};
+ abis = new String[]{nativeAPI};
}
- for (String abi: abis) {
+ for (String abi : abis) {
- File vpnExecutable = new File(context.getCacheDir(), getMiniVPNExecutableName() + "." + abi);
+ File vpnExecutable = new File(context.getCacheDir(), "c_" + getMiniVPNExecutableName() + "." + abi);
if ((vpnExecutable.exists() && vpnExecutable.canExecute()) || writeMiniVPNBinary(context, abi, vpnExecutable)) {
return vpnExecutable.getPath();
}
}
return null;
- }
+ }
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static String[] getSupportedABIsLollipop() {
return Build.SUPPORTED_ABIS;
}
- private static String getMiniVPNExecutableName()
- {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
+ private static String getMiniVPNExecutableName() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
return MINIPIEVPN;
else
return MININONPIEVPN;
}
- public static String[] replacePieWithNoPie(String[] mArgv)
- {
+ public static String[] replacePieWithNoPie(String[] mArgv) {
mArgv[0] = mArgv[0].replace(MINIPIEVPN, MININONPIEVPN);
return mArgv;
}
@@ -79,7 +77,7 @@ public class VPNLaunchHelper {
String binaryName = writeMiniVPN(c);
// Add fixed paramenters
//args.add("/data/data/de.blinkt.openvpn/lib/openvpn");
- if(binaryName==null) {
+ if (binaryName == null) {
VpnStatus.logError("Error writing minivpn binary");
return null;
}
@@ -98,8 +96,7 @@ public class VPNLaunchHelper {
try {
mvpn = context.getAssets().open(getMiniVPNExecutableName() + "." + abi);
- }
- catch (IOException errabi) {
+ } catch (IOException errabi) {
VpnStatus.logInfo("Failed getting assets for archicture " + abi);
return false;
}
@@ -107,16 +104,16 @@ public class VPNLaunchHelper {
FileOutputStream fout = new FileOutputStream(mvpnout);
- byte buf[]= new byte[4096];
+ byte buf[] = new byte[4096];
int lenread = mvpn.read(buf);
- while(lenread> 0) {
+ while (lenread > 0) {
fout.write(buf, 0, lenread);
lenread = mvpn.read(buf);
}
fout.close();
- if(!mvpnout.setExecutable(true)) {
+ if (!mvpnout.setExecutable(true)) {
VpnStatus.logError("Failed to make OpenVPN executable");
return false;
}
@@ -129,14 +126,20 @@ public class VPNLaunchHelper {
}
}
-
- public static void startOpenVpn(VpnProfile startprofile, Context context) {
- Intent startVPN = startprofile.prepareStartService(context);
- if(startVPN!=null)
- context.startService(startVPN);
- }
+ public static void startOpenVpn(VpnProfile startprofile, Context context) {
+ Intent startVPN = startprofile.prepareStartService(context);
+ if (startVPN != null) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
+ //noinspection NewApi
+ context.startForegroundService(startVPN);
+ else
+ context.startService(startVPN);
+
+ }
+ }
+
public static String getConfigFilePath(Context context) {
return context.getCacheDir().getAbsolutePath() + "/" + OVPNCONFIGFILE;
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 1e2ccba3..a9cc4f18 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java
@@ -5,45 +5,27 @@
package de.blinkt.openvpn.core;
-import android.annotation.SuppressLint;
import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.Signature;
import android.os.Build;
import android.os.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;
-import java.security.NoSuchAlgorithmException;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.util.Arrays;
-import java.util.FormatFlagsConversionMismatchException;
import java.util.LinkedList;
import java.util.Locale;
-import java.util.UnknownFormatConversionException;
+import java.util.Queue;
import java.util.Vector;
+import java.util.concurrent.ConcurrentLinkedQueue;
-import se.leap.bitmaskclient.BuildConfig;
import se.leap.bitmaskclient.R;
+import de.blinkt.openvpn.VpnProfile;
public class VpnStatus {
- public static LinkedList<LogItem> logbuffer;
+ private static final LinkedList<LogItem> logbuffer;
private static Vector<LogListener> logListener;
private static Vector<StateListener> stateListener;
@@ -55,7 +37,14 @@ public class VpnStatus {
private static int mLastStateresid = R.string.state_noprocess;
- private static long mlastByteCount[] = {0, 0, 0, 0};
+ private static HandlerThread mHandlerThread;
+
+ private static String mLastConnectedVPNUUID;
+ static boolean readFileLog =false;
+ final static java.lang.Object readFileLock = new Object();
+
+
+ public static TrafficHistory trafficHistory;
public static void logException(LogLevel ll, String context, Exception e) {
StringWriter sw = new StringWriter();
@@ -79,6 +68,9 @@ public class VpnStatus {
static final int MAXLOGENTRIES = 1000;
+ public static boolean isVPNActive() {
+ return mLastLevel != ConnectionStatus.LEVEL_AUTH_FAILED && !(mLastLevel == ConnectionStatus.LEVEL_NOTCONNECTED);
+ }
public static String getLastCleanLogMessage(Context c) {
String message = mLaststatemsg;
@@ -111,6 +103,10 @@ public class VpnStatus {
if (status.equals("NOPROCESS"))
return message;
+ if (mLastStateresid == R.string.state_waitconnectretry) {
+ return c.getString(R.string.state_waitconnectretry, mLaststatemsg);
+ }
+
String prefix = c.getString(mLastStateresid);
if (mLastStateresid == R.string.unknown_state)
message = status + message;
@@ -122,28 +118,38 @@ public class VpnStatus {
}
public static void initLogCache(File cacheDir) {
+ mHandlerThread = new HandlerThread("LogFileWriter", Thread.MIN_PRIORITY);
+ mHandlerThread.start();
+ mLogFileHandler = new LogFileHandler(mHandlerThread.getLooper());
+
+
Message m = mLogFileHandler.obtainMessage(LogFileHandler.LOG_INIT, cacheDir);
mLogFileHandler.sendMessage(m);
}
public static void flushLog() {
- mLogFileHandler.sendEmptyMessage(LogFileHandler.FLUSH_TO_DISK);
+ if (mLogFileHandler!=null)
+ mLogFileHandler.sendEmptyMessage(LogFileHandler.FLUSH_TO_DISK);
}
- public enum ConnectionStatus {
- LEVEL_CONNECTED,
- LEVEL_VPNPAUSED,
- LEVEL_CONNECTING_SERVER_REPLIED,
- LEVEL_CONNECTING_NO_SERVER_REPLY_YET,
- LEVEL_NONETWORK,
- LEVEL_NOTCONNECTED,
- LEVEL_START,
- LEVEL_AUTH_FAILED,
- LEVEL_WAITING_FOR_USER_INPUT,
- UNKNOWN_LEVEL
+ public static void setConnectedVPNProfile(String uuid) {
+ mLastConnectedVPNUUID = uuid;
+ for (StateListener sl: stateListener)
+ sl.setConnectedVPN(uuid);
}
+
+ public static String getLastConnectedVPNProfile()
+ {
+ return mLastConnectedVPNUUID;
+ }
+
+ public static void setTrafficHistory(TrafficHistory trafficHistory) {
+ VpnStatus.trafficHistory = trafficHistory;
+ }
+
+
public enum LogLevel {
INFO(2),
ERROR(-2),
@@ -163,14 +169,17 @@ public class VpnStatus {
public static LogLevel getEnumByValue(int value) {
switch (value) {
- case 1:
- return INFO;
case 2:
+ return INFO;
+ case -2:
return ERROR;
- case 3:
+ case 1:
return WARNING;
+ case 3:
+ return VERBOSE;
case 4:
return DEBUG;
+
default:
return null;
}
@@ -178,218 +187,36 @@ public class VpnStatus {
}
// keytool -printcert -jarfile de.blinkt.openvpn_85.apk
- public static final byte[] officalkey = {-58, -42, -44, -106, 90, -88, -87, -88, -52, -124, 84, 117, 66, 79, -112, -111, -46, 86, -37, 109};
- public static final byte[] officaldebugkey = {-99, -69, 45, 71, 114, -116, 82, 66, -99, -122, 50, -70, -56, -111, 98, -35, -65, 105, 82, 43};
- public static final byte[] amazonkey = {-116, -115, -118, -89, -116, -112, 120, 55, 79, -8, -119, -23, 106, -114, -85, -56, -4, 105, 26, -57};
- public static final byte[] fdroidkey = {-92, 111, -42, -46, 123, -96, -60, 79, -27, -31, 49, 103, 11, -54, -68, -27, 17, 2, 121, 104};
+ static final byte[] officalkey = {-58, -42, -44, -106, 90, -88, -87, -88, -52, -124, 84, 117, 66, 79, -112, -111, -46, 86, -37, 109};
+ 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};
private static ConnectionStatus mLastLevel = ConnectionStatus.LEVEL_NOTCONNECTED;
- private static final LogFileHandler mLogFileHandler;
+ private static 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());
+ trafficHistory = new TrafficHistory();
logInformation();
}
- public static class LogItem implements Parcelable {
- private Object[] mArgs = null;
- private String mMessage = null;
- private int mRessourceId;
- // Default log priority
- LogLevel mLevel = LogLevel.INFO;
- private long logtime = System.currentTimeMillis();
- private int mVerbosityLevel = -1;
-
- private LogItem(int ressourceId, Object[] args) {
- mRessourceId = ressourceId;
- mArgs = args;
- }
-
- public LogItem(LogLevel level, int verblevel, String message) {
- mMessage = message;
- mLevel = level;
- mVerbosityLevel = verblevel;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeArray(mArgs);
- dest.writeString(mMessage);
- dest.writeInt(mRessourceId);
- dest.writeInt(mLevel.getInt());
- dest.writeInt(mVerbosityLevel);
-
- dest.writeLong(logtime);
- }
-
- public LogItem(Parcel in) {
- mArgs = in.readArray(Object.class.getClassLoader());
- mMessage = in.readString();
- mRessourceId = in.readInt();
- mLevel = LogLevel.getEnumByValue(in.readInt());
- mVerbosityLevel = in.readInt();
- logtime = in.readLong();
- }
-
- public static final Parcelable.Creator<LogItem> CREATOR
- = new Parcelable.Creator<LogItem>() {
- public LogItem createFromParcel(Parcel in) {
- return new LogItem(in);
- }
-
- public LogItem[] newArray(int size) {
- return new LogItem[size];
- }
- };
-
- public LogItem(LogLevel loglevel, int ressourceId, Object... args) {
- mRessourceId = ressourceId;
- mArgs = args;
- mLevel = loglevel;
- }
-
-
- public LogItem(LogLevel loglevel, String msg) {
- mLevel = loglevel;
- mMessage = msg;
- }
-
-
- public LogItem(LogLevel loglevel, int ressourceId) {
- mRessourceId = ressourceId;
- mLevel = loglevel;
- }
-
- public String getString(Context c) {
- try {
- if (mMessage != null) {
- return mMessage;
- } else {
- if (c != null) {
- if (mRessourceId == R.string.mobile_info)
- return getMobileInfoString(c);
- if (mArgs == null)
- return c.getString(mRessourceId);
- else
- return c.getString(mRessourceId, mArgs);
- } else {
- String str = String.format(Locale.ENGLISH, "Log (no context) resid %d", mRessourceId);
- if (mArgs != null)
- str += TextUtils.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;
- }
-
- }
-
- public LogLevel getLogLevel() {
- return mLevel;
- }
-
- // The lint is wrong here
- @SuppressLint("StringFormatMatches")
- private String getMobileInfoString(Context c) {
- c.getPackageManager();
- String apksign = "error getting package signature";
-
- String version = "error getting version";
- try {
- @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, officalkey))
- apksign = c.getString(R.string.official_build);
- else if (Arrays.equals(digest, officaldebugkey))
- apksign = c.getString(R.string.debug_build);
- else if (Arrays.equals(digest, amazonkey))
- apksign = "amazon version";
- else if (Arrays.equals(digest, fdroidkey))
- apksign = "F-Droid built and signed version";
- else
- apksign = c.getString(R.string.built_by, cert.getSubjectX500Principal().getName());
-
- PackageInfo packageinfo = c.getPackageManager().getPackageInfo(c.getPackageName(), 0);
- version = packageinfo.versionName;
-
- } catch (NameNotFoundException | 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;
- }
- }
-
public interface LogListener {
void newLog(LogItem logItem);
}
public interface StateListener {
void updateState(String state, String logmessage, int localizedResId, ConnectionStatus level);
+
+ void setConnectedVPN(String uuid);
}
public interface ByteCountListener {
@@ -404,12 +231,20 @@ public class VpnStatus {
public synchronized static void clearLog() {
logbuffer.clear();
logInformation();
- mLogFileHandler.sendEmptyMessage(LogFileHandler.TRIM_LOG_FILE);
+ if (mLogFileHandler != null)
+ mLogFileHandler.sendEmptyMessage(LogFileHandler.TRIM_LOG_FILE);
}
private static void logInformation() {
+ String nativeAPI;
+ try {
+ nativeAPI = NativeUtils.getNativeAPI();
+ } catch (UnsatisfiedLinkError ignore) {
+ nativeAPI = "error";
+ }
+
logInfo(R.string.mobile_info, Build.MODEL, Build.BOARD, Build.BRAND, Build.VERSION.SDK_INT,
- NativeUtils.getNativeAPI(), Build.VERSION.RELEASE, Build.ID, Build.FINGERPRINT, "", "");
+ nativeAPI, Build.VERSION.RELEASE, Build.ID, Build.FINGERPRINT, "", "");
}
public synchronized static void addLogListener(LogListener ll) {
@@ -421,7 +256,8 @@ public class VpnStatus {
}
public synchronized static void addByteCountListener(ByteCountListener bcl) {
- bcl.updateByteCount(mlastByteCount[0], mlastByteCount[1], mlastByteCount[2], mlastByteCount[3]);
+ TrafficHistory.LastDiff diff = trafficHistory.getLastDiff(null);
+ bcl.updateByteCount(diff.getIn(), diff.getOut(), diff.getDiffIn(),diff.getDiffOut());
byteCountListener.add(bcl);
}
@@ -525,7 +361,7 @@ public class VpnStatus {
}
- public static void updateStateString(String state, String msg) {
+ static void updateStateString(String state, String msg) {
int rid = getLocalizedState(state);
ConnectionStatus level = getLevel(state);
updateStateString(state, msg, rid, level);
@@ -549,7 +385,7 @@ public class VpnStatus {
for (StateListener sl : stateListener) {
sl.updateState(state, msg, resid, level);
}
- //newLogItem(new LogItem((LogLevel.DEBUG), String.format("New OpenVPN Status (%s->%s): %s",state,level.toString(),msg)));
+ newLogItem(new LogItem((LogLevel.DEBUG), String.format("New OpenVPN Status (%s->%s): %s",state,level.toString(),msg)));
}
public static void logInfo(String message) {
@@ -568,7 +404,7 @@ public class VpnStatus {
newLogItem(new LogItem(LogLevel.DEBUG, resourceId, args));
}
- private static void newLogItem(LogItem logItem) {
+ static void newLogItem(LogItem logItem) {
newLogItem(logItem, false);
}
@@ -578,18 +414,21 @@ public class VpnStatus {
logbuffer.addFirst(logItem);
} else {
logbuffer.addLast(logItem);
- Message m = mLogFileHandler.obtainMessage(LogFileHandler.LOG_MESSAGE, logItem);
- mLogFileHandler.sendMessage(m);
+ if (mLogFileHandler != null) {
+ 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 (mLogFileHandler != null)
+ mLogFileHandler.sendMessage(mLogFileHandler.obtainMessage(LogFileHandler.TRIM_LOG_FILE));
}
- if (BuildConfig.DEBUG && !cachedLine)
- Log.d("OpenVPN", logItem.getString(null));
+ //if (BuildConfig.DEBUG && !cachedLine && !BuildConfig.FLAVOR.equals("test"))
+ // Log.d("OpenVPN", logItem.getString(null));
for (LogListener ll : logListener) {
@@ -627,17 +466,10 @@ public class VpnStatus {
public static synchronized void updateByteCount(long in, long out) {
- long lastIn = mlastByteCount[0];
- long lastOut = mlastByteCount[1];
- long diffIn = mlastByteCount[2] = Math.max(0, in - lastIn);
- long diffOut = mlastByteCount[3] = Math.max(0, out - lastOut);
-
+ TrafficHistory.LastDiff diff = trafficHistory.add(in, out);
- mlastByteCount = new long[]{in, out, diffIn, diffOut};
for (ByteCountListener bcl : byteCountListener) {
- bcl.updateByteCount(in, out, diffIn, diffOut);
+ bcl.updateByteCount(in, out, diff.getDiffIn(), diff.getDiffOut());
}
}
-
-
}
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 4048f0e0..9e2060fd 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/X509Utils.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/X509Utils.java
@@ -5,6 +5,7 @@
package de.blinkt.openvpn.core;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.text.TextUtils;
@@ -106,14 +107,14 @@ public class X509Utils {
// 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);
+ return res.getQuantityString(R.plurals.months_left, (int) months, months);
} else if (timeLeft > 72 * 3600 * 1000) {
long days = timeLeft / (24 * 3600 * 1000);
- return res.getString(R.string.days_left, days);
+ return res.getQuantityString(R.plurals.days_left, (int) days, days);
} else {
long hours = timeLeft / (3600 * 1000);
- return res.getString(R.string.hours_left, hours);
+ return res.getQuantityString(R.plurals.hours_left, (int)hours, hours);
}
}
@@ -131,7 +132,7 @@ public class X509Utils {
/* Hack so we do not have to ship a whole Spongy/bouncycastle */
Exception exp=null;
try {
- Class X509NameClass = Class.forName("com.android.org.bouncycastle.asn1.x509.X509Name");
+ @SuppressLint("PrivateApi") Class X509NameClass = Class.forName("com.android.org.bouncycastle.asn1.x509.X509Name");
Method getInstance = X509NameClass.getMethod("getInstance",Object.class);
Hashtable defaultSymbols = (Hashtable) X509NameClass.getField("DefaultSymbols").get(X509NameClass);
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 bbd52a34..a1fc7cdc 100644
--- a/app/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java
+++ b/app/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java
@@ -16,6 +16,7 @@ import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.res.Resources;
import android.database.DataSetObserver;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
@@ -56,12 +57,13 @@ import de.blinkt.openvpn.LaunchVPN;
import se.leap.bitmaskclient.R;
import de.blinkt.openvpn.VpnProfile;
import de.blinkt.openvpn.activities.DisconnectVPN;
+import de.blinkt.openvpn.core.ConnectionStatus;
import de.blinkt.openvpn.core.OpenVPNManagement;
import de.blinkt.openvpn.core.OpenVPNService;
+import de.blinkt.openvpn.core.Preferences;
import de.blinkt.openvpn.core.ProfileManager;
import de.blinkt.openvpn.core.VpnStatus;
-import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus;
-import de.blinkt.openvpn.core.VpnStatus.LogItem;
+import de.blinkt.openvpn.core.LogItem;
import de.blinkt.openvpn.core.VpnStatus.LogListener;
import de.blinkt.openvpn.core.VpnStatus.StateListener;
@@ -117,8 +119,9 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar.
@Override
public void updateByteCount(long in, long out, long diffIn, long diffOut) {
//%2$s/s %1$s - ↑%4$s/s %3$s
- final String down = String.format("%2$s/s %1$s", humanReadableByteCount(in, false), humanReadableByteCount(diffIn / OpenVPNManagement.mBytecountInterval, true));
- final String up = String.format("%2$s/s %1$s", humanReadableByteCount(out, false), humanReadableByteCount(diffOut / OpenVPNManagement.mBytecountInterval, true));
+ Resources res = getActivity().getResources();
+ final String down = String.format("%2$s %1$s", humanReadableByteCount(in, false, res), humanReadableByteCount(diffIn / OpenVPNManagement.mBytecountInterval, true, res));
+ final String up = String.format("%2$s %1$s", humanReadableByteCount(out, false, res), humanReadableByteCount(diffOut / OpenVPNManagement.mBytecountInterval, true, res));
if (mUpStatus != null && mDownStatus != null) {
if (getActivity() != null) {
@@ -429,33 +432,34 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar.
Intent intent = new Intent(getActivity(), DisconnectVPN.class);
startActivity(intent);
return true;
- } else if(item.getItemId()==R.id.send) {
- ladapter.shareLog();
- } else if(item.getItemId()==R.id.edit_vpn) {
- VpnProfile lastConnectedprofile = ProfileManager.getLastConnectedVpn();
-
- if(lastConnectedprofile!=null) {
- Intent vprefintent = new Intent(getActivity(),Dashboard.class)
- .putExtra(VpnProfile.EXTRA_PROFILEUUID,lastConnectedprofile.getUUIDString());
- startActivityForResult(vprefintent,START_VPN_CONFIG);
- } else {
- Toast.makeText(getActivity(), R.string.log_no_last_vpn, Toast.LENGTH_LONG).show();
- }
- } else if(item.getItemId() == R.id.toggle_time) {
- showHideOptionsPanel();
- } else if(item.getItemId() == android.R.id.home) {
- // This is called when the Home (Up) button is pressed
- // in the Action Bar.
- Intent parentActivityIntent = new Intent(getActivity(), Dashboard.class);
- parentActivityIntent.addFlags(
- Intent.FLAG_ACTIVITY_CLEAR_TOP |
- Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivity(parentActivityIntent);
- getActivity().finish();
- return true;
-
- }
- return super.onOptionsItemSelected(item);
+ } else if (item.getItemId() == R.id.send) {
+ ladapter.shareLog();
+ } else if (item.getItemId() == R.id.edit_vpn) {
+ VpnProfile lastConnectedprofile = ProfileManager.get(getActivity(), VpnStatus.getLastConnectedVPNProfile());
+
+ 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() {
@@ -500,13 +504,16 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar.
@Override
public void onResume() {
super.onResume();
- VpnStatus.addStateListener(this);
- VpnStatus.addByteCountListener(this);
Intent intent = new Intent(getActivity(), OpenVPNService.class);
intent.setAction(OpenVPNService.START_SERVICE);
-
}
+ @Override
+ public void onStart() {
+ super.onStart();
+ VpnStatus.addStateListener(this);
+ VpnStatus.addByteCountListener(this);
+ }
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
@@ -603,7 +610,7 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar.
mClearLogCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- PreferenceManager.getDefaultSharedPreferences(getActivity()).edit().putBoolean(LaunchVPN.CLEARLOG, isChecked).apply();
+ Preferences.getDefaultSharedPreferences(getActivity()).edit().putBoolean(LaunchVPN.CLEARLOG, isChecked).apply();
}
});
@@ -628,7 +635,14 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar.
}
@Override
- public void onAttach(Activity activity) {
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ // Scroll to the end of the list end
+ //getListView().setSelection(getListView().getAdapter().getCount()-1);
+ }
+
+ @Override
+ public void onAttach(Context activity) {
super.onAttach(activity);
if (getResources().getBoolean(R.bool.logSildersAlwaysVisible)) {
mShowOptionsLayout = true;
@@ -660,13 +674,17 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar.
mSpeedView.setText(cleanLogMessage);
}
if (mConnectStatus != null)
- mConnectStatus.setText(getString(resId));
+ mConnectStatus.setText(cleanLogMessage);
}
}
});
}
}
+ @Override
+ public void setConnectedVPN(String uuid) {
+ }
+
@Override
public void onDestroy() {