summaryrefslogtreecommitdiff
path: root/app/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java')
-rw-r--r--app/src/main/java/de/blinkt/openvpn/LaunchVPN.java224
-rw-r--r--app/src/main/java/de/blinkt/openvpn/VpnProfile.java955
-rw-r--r--app/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java82
-rw-r--r--app/src/main/java/de/blinkt/openvpn/activities/LogWindow.java32
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/CIDRIP.java70
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java (renamed from app/src/main/java/se/leap/openvpn/ConfigParser.java)356
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java243
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java14
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java13
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java300
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java24
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java174
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java (renamed from app/src/main/java/se/leap/openvpn/OpenVpnManagementThread.java)1190
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/OpenVpnService.java765
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/PRNGFixes.java334
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/ProfileManager.java (renamed from app/src/main/java/se/leap/openvpn/ProfileManager.java)21
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/ProxyDetection.java (renamed from app/src/main/java/se/leap/openvpn/ProxyDetection.java)7
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java (renamed from app/src/main/java/se/leap/openvpn/VPNLaunchHelper.java)21
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java540
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/X509Utils.java155
-rw-r--r--app/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java663
-rw-r--r--app/src/main/java/de/blinkt/openvpn/views/SeekBarTicks.java69
-rw-r--r--app/src/main/java/org/spongycastle/util/io/pem/PemReader.java84
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/Dashboard.java6
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/EIP.java73
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java33
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/EipStatusReceiver.java17
-rw-r--r--app/src/main/java/se/leap/openvpn/CIDRIP.java58
-rw-r--r--app/src/main/java/se/leap/openvpn/LICENSE.txt24
-rw-r--r--app/src/main/java/se/leap/openvpn/LaunchVPN.java385
-rw-r--r--app/src/main/java/se/leap/openvpn/LogWindow.java340
-rw-r--r--app/src/main/java/se/leap/openvpn/NetworkSateReceiver.java86
-rw-r--r--app/src/main/java/se/leap/openvpn/OpenVPN.java250
-rw-r--r--app/src/main/java/se/leap/openvpn/OpenVPNThread.java130
-rw-r--r--app/src/main/java/se/leap/openvpn/OpenVpnService.java513
-rw-r--r--app/src/main/java/se/leap/openvpn/VpnProfile.java758
36 files changed, 5722 insertions, 3287 deletions
diff --git a/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java b/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java
new file mode 100644
index 00000000..f8487891
--- /dev/null
+++ b/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java
@@ -0,0 +1,224 @@
+package de.blinkt.openvpn;
+
+import se.leap.bitmaskclient.R;
+
+import se.leap.bitmaskclient.R;
+
+import java.io.IOException;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.ActivityNotFoundException;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.VpnService;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.text.InputType;
+import android.text.TextUtils;
+import android.text.method.PasswordTransformationMethod;
+import android.view.View;
+import android.widget.*;
+
+import de.blinkt.openvpn.activities.LogWindow;
+import de.blinkt.openvpn.core.VpnStatus;
+import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus;
+import de.blinkt.openvpn.core.ProfileManager;
+import de.blinkt.openvpn.core.VPNLaunchHelper;
+
+/**
+ * This Activity actually handles two stages of a launcher shortcut's life cycle.
+ *
+ * 1. Your application offers to provide shortcuts to the launcher. When
+ * the user installs a shortcut, an activity within your application
+ * generates the actual shortcut and returns it to the launcher, where it
+ * is shown to the user as an icon.
+ *
+ * 2. Any time the user clicks on an installed shortcut, an intent is sent.
+ * Typically this would then be handled as necessary by an activity within
+ * your application.
+ *
+ * We handle stage 1 (creating a shortcut) by simply sending back the information (in the form
+ * of an {@link android.content.Intent} that the launcher will use to create the shortcut.
+ *
+ * You can also implement this in an interactive way, by having your activity actually present
+ * UI for the user to select the specific nature of the shortcut, such as a contact, picture, URL,
+ * media item, or action.
+ *
+ * We handle stage 2 (responding to a shortcut) in this sample by simply displaying the contents
+ * of the incoming {@link android.content.Intent}.
+ *
+ * In a real application, you would probably use the shortcut intent to display specific content
+ * or start a particular operation.
+ */
+public class LaunchVPN extends Activity {
+
+ public static final String EXTRA_KEY = "de.blinkt.openvpn.shortcutProfileUUID";
+ public static final String EXTRA_NAME = "de.blinkt.openvpn.shortcutProfileName";
+ public static final String EXTRA_HIDELOG = "de.blinkt.openvpn.showNoLogWindow";
+
+ private static final int START_VPN_PROFILE= 70;
+
+
+ private ProfileManager mPM;
+ private VpnProfile mSelectedProfile;
+ private boolean mhideLog=false;
+
+ private boolean mCmfixed=false;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ mPM =ProfileManager.getInstance(this);
+
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ // Resolve the intent
+
+ final Intent intent = getIntent();
+ final String action = intent.getAction();
+
+ // If the intent is a request to create a shortcut, we'll do that and exit
+
+
+ if(Intent.ACTION_MAIN.equals(action)) {
+ // we got called to be the starting point, most likely a shortcut
+ String shortcutUUID = intent.getStringExtra( EXTRA_KEY);
+ String shortcutName = intent.getStringExtra( EXTRA_NAME);
+ mhideLog = intent.getBooleanExtra(EXTRA_HIDELOG, false);
+
+ VpnProfile profileToConnect = ProfileManager.get(this,shortcutUUID);
+ if(shortcutName != null && profileToConnect ==null)
+ profileToConnect = ProfileManager.getInstance(this).getProfileByName(shortcutName);
+
+ if(profileToConnect ==null) {
+ VpnStatus.logError(R.string.shortcut_profile_notfound);
+ // show Log window to display error
+ showLogWindow();
+ finish();
+ return;
+ }
+
+ mSelectedProfile = profileToConnect;
+ launchVPN();
+
+ }
+ }
+
+ @Override
+ protected void onActivityResult (int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ if(requestCode==START_VPN_PROFILE) {
+ if(resultCode == Activity.RESULT_OK) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ boolean showlogwindow = prefs.getBoolean("showlogwindow", true);
+
+ if(!mhideLog && showlogwindow)
+ showLogWindow();
+ new startOpenVpnThread().start();
+ } else if (resultCode == Activity.RESULT_CANCELED) {
+ // User does not want us to start, so we just vanish
+ VpnStatus.updateStateString("USER_VPN_PERMISSION_CANCELLED", "", R.string.state_user_vpn_permission_cancelled,
+ ConnectionStatus.LEVEL_NOTCONNECTED);
+
+ finish();
+ }
+ }
+ }
+ void showLogWindow() {
+
+ Intent startLW = new Intent(getBaseContext(),LogWindow.class);
+ startLW.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+ startActivity(startLW);
+
+ }
+
+ void showConfigErrorDialog(int vpnok) {
+ AlertDialog.Builder d = new AlertDialog.Builder(this);
+ d.setTitle(R.string.config_error_found);
+ d.setMessage(vpnok);
+ d.setPositiveButton(android.R.string.ok, new OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ finish();
+
+ }
+ });
+ d.show();
+ }
+
+ void launchVPN () {
+ int vpnok = mSelectedProfile.checkProfile(this);
+ if(vpnok!= R.string.no_error_found) {
+ showConfigErrorDialog(vpnok);
+ return;
+ }
+
+ Intent intent = VpnService.prepare(this);
+ // Check if we want to fix /dev/tun
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ boolean usecm9fix = prefs.getBoolean("useCM9Fix", false);
+ boolean loadTunModule = prefs.getBoolean("loadTunModule", false);
+
+ if(loadTunModule)
+ execeuteSUcmd("insmod /system/lib/modules/tun.ko");
+
+ if(usecm9fix && !mCmfixed ) {
+ execeuteSUcmd("chown system /dev/tun");
+ }
+
+
+ if (intent != null) {
+ VpnStatus.updateStateString("USER_VPN_PERMISSION", "", R.string.state_user_vpn_permission,
+ ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT);
+ // Start the query
+ try {
+ startActivityForResult(intent, START_VPN_PROFILE);
+ } catch (ActivityNotFoundException ane) {
+ // Shame on you Sony! At least one user reported that
+ // an official Sony Xperia Arc S image triggers this exception
+ VpnStatus.logError(R.string.no_vpn_support_image);
+ showLogWindow();
+ }
+ } else {
+ onActivityResult(START_VPN_PROFILE, Activity.RESULT_OK, null);
+ }
+
+ }
+
+ private void execeuteSUcmd(String command) {
+ ProcessBuilder pb = new ProcessBuilder("su","-c",command);
+ try {
+ Process p = pb.start();
+ int ret = p.waitFor();
+ if(ret ==0)
+ mCmfixed=true;
+ } catch (InterruptedException e) {
+ VpnStatus.logException("SU command", e);
+
+ } catch (IOException e) {
+ VpnStatus.logException("SU command", e);
+ }
+ }
+
+ private class startOpenVpnThread extends Thread {
+
+ @Override
+ public void run() {
+ VPNLaunchHelper.startOpenVpn(mSelectedProfile, getBaseContext());
+ finish();
+
+ }
+
+ }
+
+
+}
diff --git a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java
new file mode 100644
index 00000000..93d0d386
--- /dev/null
+++ b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java
@@ -0,0 +1,955 @@
+package de.blinkt.openvpn;
+
+import se.leap.bitmaskclient.R;
+
+import se.leap.bitmaskclient.R;
+
+import se.leap.bitmaskclient.Dashboard;
+import se.leap.bitmaskclient.EIP;
+import se.leap.bitmaskclient.Provider;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.preference.PreferenceManager;
+import android.security.KeyChain;
+import android.security.KeyChainException;
+import android.util.Base64;
+
+import de.blinkt.openvpn.core.NativeUtils;
+import de.blinkt.openvpn.core.VpnStatus;
+import de.blinkt.openvpn.core.OpenVpnService;
+import de.blinkt.openvpn.core.X509Utils;
+import org.spongycastle.util.io.pem.PemObject;
+import org.spongycastle.util.io.pem.PemWriter;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import java.io.*;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.Locale;
+import java.util.UUID;
+import java.util.Vector;
+
+public class VpnProfile implements Serializable {
+ // Note that this class cannot be moved to core where it belongs since
+ // the profile loading depends on it being here
+ // The Serializable documentation mentions that class name change are possible
+ // but the how is unclear
+ //
+ transient public static final long MAX_EMBED_FILE_SIZE = 2048*1024; // 2048kB
+ // Don't change this, not all parts of the program use this constant
+ public static final String EXTRA_PROFILEUUID = "de.blinkt.openvpn.profileUUID";
+ public static final String INLINE_TAG = "[[INLINE]]";
+ public static final String DISPLAYNAME_TAG = "[[NAME]]";
+ public static final String MINIVPN = "miniopenvpn";
+ private static final long serialVersionUID = 7085688938959334563L;
+ private static final String OVPNCONFIGFILE = "android.conf";
+ public static final int MAXLOGLEVEL = 4;
+ public static final int CURRENT_PROFILE_VERSION = 2;
+ public static String DEFAULT_DNS1 = "8.8.8.8";
+ public static String DEFAULT_DNS2 = "8.8.4.4";
+
+ public transient String mTransientPW = null;
+ public transient String mTransientPCKS12PW = null;
+
+
+ public static final int TYPE_CERTIFICATES = 0;
+ public static final int TYPE_PKCS12 = 1;
+ public static final int TYPE_KEYSTORE = 2;
+ public static final int TYPE_USERPASS = 3;
+ public static final int TYPE_STATICKEYS = 4;
+ public static final int TYPE_USERPASS_CERTIFICATES = 5;
+ public static final int TYPE_USERPASS_PKCS12 = 6;
+ public static final int TYPE_USERPASS_KEYSTORE = 7;
+ public static final int X509_VERIFY_TLSREMOTE = 0;
+ public static final int X509_VERIFY_TLSREMOTE_COMPAT_NOREMAPPING = 1;
+ public static final int X509_VERIFY_TLSREMOTE_DN = 2;
+ public static final int X509_VERIFY_TLSREMOTE_RDN = 3;
+ public static final int X509_VERIFY_TLSREMOTE_RDN_PREFIX = 4;
+ // variable named wrong and should haven beeen transient
+ // but needs to keep wrong name to guarante loading of old
+ // profiles
+ public transient boolean profileDleted = false;
+ public int mAuthenticationType = TYPE_CERTIFICATES;
+ public String mName;
+ public String mAlias;
+ public String mClientCertFilename;
+ public String mTLSAuthDirection = "";
+ public String mTLSAuthFilename;
+ public String mClientKeyFilename;
+ public String mCaFilename;
+ public boolean mUseLzo = true;
+ public String mServerPort = "1194";
+ public boolean mUseUdp = true;
+ public String mPKCS12Filename;
+ public String mPKCS12Password;
+ public boolean mUseTLSAuth = false;
+ public String mServerName = "openvpn.blinkt.de";
+ public String mDNS1 = DEFAULT_DNS1;
+ public String mDNS2 = DEFAULT_DNS2;
+ public String mIPv4Address;
+ public String mIPv6Address;
+ public boolean mOverrideDNS = false;
+ public String mSearchDomain = "blinkt.de";
+ public boolean mUseDefaultRoute = true;
+ public boolean mUsePull = true;
+ public String mCustomRoutes;
+ public boolean mCheckRemoteCN = false;
+ public boolean mExpectTLSCert = true;
+ public String mRemoteCN = "";
+ public String mPassword = "";
+ public String mUsername = "";
+ public boolean mRoutenopull = false;
+ public boolean mUseRandomHostname = false;
+ public boolean mUseFloat = false;
+ public boolean mUseCustomConfig = false;
+ public String mCustomConfigOptions = "";
+ public String mVerb = "1"; //ignored
+ public String mCipher = "";
+ public boolean mNobind = false;
+ public boolean mUseDefaultRoutev6 = true;
+ public String mCustomRoutesv6 = "";
+ public String mKeyPassword = "";
+ public boolean mPersistTun = false;
+ public String mConnectRetryMax = "5";
+ public String mConnectRetry = "5";
+ public boolean mUserEditable = true;
+ public String mAuth = "";
+ public int mX509AuthType = X509_VERIFY_TLSREMOTE_RDN;
+ private transient PrivateKey mPrivateKey;
+ // Public attributes, since I got mad with getter/setter
+ // set members to default values
+ private UUID mUuid;
+ public boolean mAllowLocalLAN;
+ private int mProfileVersion;
+ public String mExcludedRoutes;
+ public String mExcludedRoutesv6;
+
+ public VpnProfile(String name) {
+ mUuid = UUID.randomUUID();
+ mName = name;
+ mProfileVersion = CURRENT_PROFILE_VERSION;
+ }
+
+ public static String openVpnEscape(String unescaped) {
+ if (unescaped == null)
+ return null;
+ String escapedString = unescaped.replace("\\", "\\\\");
+ escapedString = escapedString.replace("\"", "\\\"");
+ escapedString = escapedString.replace("\n", "\\n");
+
+ if (escapedString.equals(unescaped) && !escapedString.contains(" ") &&
+ !escapedString.contains("#") && !escapedString.contains(";"))
+ return unescaped;
+ else
+ return '"' + escapedString + '"';
+ }
+
+ public void clearDefaults() {
+ mServerName = "unknown";
+ mUsePull = false;
+ mUseLzo = false;
+ mUseDefaultRoute = false;
+ mUseDefaultRoutev6 = false;
+ mExpectTLSCert = false;
+ mPersistTun = false;
+ mAllowLocalLAN = true;
+ }
+
+ public UUID getUUID() {
+ return mUuid;
+
+ }
+
+ public String getName() {
+ if (mName==null)
+ return "No profile name";
+ return mName;
+ }
+
+ public void upgradeProfile(){
+ if(mProfileVersion< 2) {
+ /* default to the behaviour the OS used */
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
+ mAllowLocalLAN = true;
+ else
+ mAllowLocalLAN = false;
+ }
+
+ mProfileVersion= CURRENT_PROFILE_VERSION;
+ }
+
+ public String getConfigFile(Context context, boolean configForOvpn3) {
+
+ File cacheDir = context.getCacheDir();
+ String cfg = "";
+
+ // Enable managment interface
+ cfg += "# Enables connection to GUI\n";
+ cfg += "management ";
+
+ cfg += cacheDir.getAbsolutePath() + "/" + "mgmtsocket";
+ cfg += " unix\n";
+ cfg += "management-client\n";
+ // Not needed, see updated man page in 2.3
+ //cfg += "management-signal\n";
+ cfg += "management-query-passwords\n";
+ cfg += "management-hold\n\n";
+
+ if (!configForOvpn3)
+ cfg += String.format("setenv IV_GUI_VER %s \n", openVpnEscape(getVersionEnvString(context)));
+
+ cfg += "machine-readable-output\n";
+
+
+ boolean useTLSClient = (mAuthenticationType != TYPE_STATICKEYS);
+
+ if (useTLSClient && mUsePull)
+ cfg += "client\n";
+ else if (mUsePull)
+ cfg += "pull\n";
+ else if (useTLSClient)
+ cfg += "tls-client\n";
+
+
+ //cfg += "verb " + mVerb + "\n";
+ cfg += "verb " + MAXLOGLEVEL + "\n";
+
+ if (mConnectRetryMax == null) {
+ mConnectRetryMax = "5";
+ }
+
+ if (!mConnectRetryMax.equals("-1"))
+ cfg += "connect-retry-max " + mConnectRetryMax + "\n";
+
+ if (mConnectRetry == null)
+ mConnectRetry = "5";
+
+
+ cfg += "connect-retry " + mConnectRetry + "\n";
+
+ cfg += "resolv-retry 60\n";
+
+
+ // We cannot use anything else than tun
+ cfg += "dev tun\n";
+
+ // Server Address
+ cfg += "remote ";
+ cfg += mServerName;
+ cfg += " ";
+ cfg += mServerPort;
+ if (mUseUdp)
+ cfg += " udp\n";
+ else
+ cfg += " tcp-client\n";
+
+
+ android.util.Log.d("vpnprofile", Integer.toString(mAuthenticationType));
+ switch (mAuthenticationType) {
+ case VpnProfile.TYPE_USERPASS_CERTIFICATES:
+ cfg += "auth-user-pass\n";
+ case VpnProfile.TYPE_CERTIFICATES:
+ // Ca
+ // cfg += insertFileData("ca", mCaFilename);
+
+ // // Client Cert + Key
+ // cfg += insertFileData("key", mClientKeyFilename);
+ // cfg += insertFileData("cert", mClientCertFilename);
+ // FIXME This is all we need...The whole switch statement can go...
+ SharedPreferences preferences = context.getSharedPreferences(Dashboard.SHARED_PREFERENCES, context.MODE_PRIVATE);
+ cfg+="<ca>\n"+preferences.getString(Provider.CA_CERT, "")+"\n</ca>\n";
+ cfg+="<key>\n"+preferences.getString(EIP.PRIVATE_KEY, "")+"\n</key>\n";
+ cfg+="<cert>\n"+preferences.getString(EIP.CERTIFICATE, "")+"\n</cert>\n";
+ break;
+ case VpnProfile.TYPE_USERPASS_PKCS12:
+ cfg += "auth-user-pass\n";
+ case VpnProfile.TYPE_PKCS12:
+ cfg += insertFileData("pkcs12", mPKCS12Filename);
+ break;
+
+ case VpnProfile.TYPE_USERPASS_KEYSTORE:
+ cfg += "auth-user-pass\n";
+ case VpnProfile.TYPE_KEYSTORE:
+ if (!configForOvpn3) {
+ String[] ks = getKeyStoreCertificates(context);
+ cfg += "### From Keystore ####\n";
+ if (ks != null) {
+ cfg += "<ca>\n" + ks[0] + "\n</ca>\n";
+ if (ks[1] != null)
+ cfg += "<extra-certs>\n" + ks[1] + "\n</extra-certs>\n";
+ cfg += "<cert>\n" + ks[2] + "\n</cert>\n";
+ cfg += "management-external-key\n";
+ } else {
+ cfg += context.getString(R.string.keychain_access) + "\n";
+ if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN)
+ if (!mAlias.matches("^[a-zA-Z0-9]$"))
+ cfg += context.getString(R.string.jelly_keystore_alphanumeric_bug) + "\n";
+ }
+ }
+ break;
+ case VpnProfile.TYPE_USERPASS:
+ cfg += "auth-user-pass\n";
+ cfg += insertFileData("ca", mCaFilename);
+ }
+
+ if (mUseLzo) {
+ cfg += "comp-lzo\n";
+ }
+
+ if (mUseTLSAuth) {
+ if (mAuthenticationType == TYPE_STATICKEYS)
+ cfg += insertFileData("secret", mTLSAuthFilename);
+ else
+ cfg += insertFileData("tls-auth", mTLSAuthFilename);
+
+ if (nonNull(mTLSAuthDirection)) {
+ cfg += "key-direction ";
+ cfg += mTLSAuthDirection;
+ cfg += "\n";
+ }
+
+ }
+
+ if (!mUsePull) {
+ if (nonNull(mIPv4Address))
+ cfg += "ifconfig " + cidrToIPAndNetmask(mIPv4Address) + "\n";
+
+ if (nonNull(mIPv6Address))
+ cfg += "ifconfig-ipv6 " + mIPv6Address + "\n";
+ }
+
+ if (mUsePull && mRoutenopull)
+ cfg += "route-nopull\n";
+
+ String routes = "";
+
+ if (mUseDefaultRoute)
+ routes += "route 0.0.0.0 0.0.0.0 vpn_gateway\n";
+ else
+ {
+ for (String route : getCustomRoutes(mCustomRoutes)) {
+ routes += "route " + route + " vpn_gateway\n";
+ }
+
+ for (String route: getCustomRoutes(mExcludedRoutes)) {
+ routes += "route " + route + " net_gateway\n";
+ }
+ }
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT && !mAllowLocalLAN)
+ cfg+="redirect-private block-local\n";
+ else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && mAllowLocalLAN)
+ cfg+="redirect-private unblock-local\n";
+
+
+ if (mUseDefaultRoutev6)
+ cfg += "route-ipv6 ::/0\n";
+ else
+ for (String route : getCustomRoutesv6(mCustomRoutesv6)) {
+ routes += "route-ipv6 " + route + "\n";
+ }
+
+ cfg += routes;
+
+ if (mOverrideDNS || !mUsePull) {
+ if (nonNull(mDNS1))
+ cfg += "dhcp-option DNS " + mDNS1 + "\n";
+ if (nonNull(mDNS2))
+ cfg += "dhcp-option DNS " + mDNS2 + "\n";
+ if (nonNull(mSearchDomain))
+ cfg += "dhcp-option DOMAIN " + mSearchDomain + "\n";
+
+ }
+
+ if (mNobind)
+ cfg += "nobind\n";
+
+
+ // Authentication
+ if (mAuthenticationType != TYPE_STATICKEYS) {
+ if (mCheckRemoteCN) {
+ if (mRemoteCN == null || mRemoteCN.equals(""))
+ cfg += "verify-x509-name " + mServerName + " name\n";
+ else
+ switch (mX509AuthType) {
+
+ // 2.2 style x509 checks
+ case X509_VERIFY_TLSREMOTE_COMPAT_NOREMAPPING:
+ cfg += "compat-names no-remapping\n";
+ case X509_VERIFY_TLSREMOTE:
+ cfg += "tls-remote " + openVpnEscape(mRemoteCN) + "\n";
+ break;
+
+ case X509_VERIFY_TLSREMOTE_RDN:
+ cfg += "verify-x509-name " + openVpnEscape(mRemoteCN) + " name\n";
+ break;
+
+ case X509_VERIFY_TLSREMOTE_RDN_PREFIX:
+ cfg += "verify-x509-name " + openVpnEscape(mRemoteCN) + " name-prefix\n";
+ break;
+
+ case X509_VERIFY_TLSREMOTE_DN:
+ cfg += "verify-x509-name " + openVpnEscape(mRemoteCN) + "\n";
+ break;
+ }
+ }
+ if (mExpectTLSCert)
+ cfg += "remote-cert-tls server\n";
+ }
+
+ if (nonNull(mCipher)) {
+ cfg += "cipher " + mCipher + "\n";
+ }
+
+ if (nonNull(mAuth)) {
+ cfg += "auth " + mAuth + "\n";
+ }
+
+ // Obscure Settings dialog
+ if (mUseRandomHostname)
+ cfg += "#my favorite options :)\nremote-random-hostname\n";
+
+ if (mUseFloat)
+ cfg += "float\n";
+
+ if (mPersistTun) {
+ cfg += "persist-tun\n";
+ cfg += "# persist-tun also enables pre resolving to avoid DNS resolve problem\n";
+ cfg += "preresolve\n";
+ }
+
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ boolean usesystemproxy = prefs.getBoolean("usesystemproxy", true);
+ if (usesystemproxy) {
+ cfg += "# Use system proxy setting\n";
+ cfg += "management-query-proxy\n";
+ }
+
+
+ if (mUseCustomConfig) {
+ cfg += "# Custom configuration options\n";
+ cfg += "# You are on your on own here :)\n";
+ cfg += mCustomConfigOptions;
+ cfg += "\n";
+
+ }
+
+
+ return cfg;
+ }
+
+ public String getVersionEnvString(Context c) {
+ String version = "unknown";
+ try {
+ PackageInfo packageinfo = c.getPackageManager().getPackageInfo(c.getPackageName(), 0);
+ version = packageinfo.versionName;
+ } catch (PackageManager.NameNotFoundException e) {
+ VpnStatus.logException(e);
+ }
+ return String.format(Locale.US, "%s %s", c.getPackageName(), version);
+
+ }
+
+ //! Put inline data inline and other data as normal escaped filename
+ private String insertFileData(String cfgentry, String filedata) {
+ if (filedata == null) {
+ // TODO: generate good error
+ return String.format("%s %s\n", cfgentry, "missing");
+ } else if (isEmbedded(filedata)) {
+ String dataWithOutHeader = getEmbeddedContent(filedata);
+ return String.format(Locale.ENGLISH, "<%s>\n%s\n</%s>\n", cfgentry, dataWithOutHeader, cfgentry);
+ } else {
+ return String.format(Locale.ENGLISH, "%s %s\n", cfgentry, openVpnEscape(filedata));
+ }
+ }
+
+ private boolean nonNull(String val) {
+ if (val == null || val.equals(""))
+ return false;
+ else
+ return true;
+ }
+
+ private Collection<String> getCustomRoutes(String routes) {
+ Vector<String> cidrRoutes = new Vector<String>();
+ if (routes == null) {
+ // No routes set, return empty vector
+ return cidrRoutes;
+ }
+ for (String route : routes.split("[\n \t]")) {
+ if (!route.equals("")) {
+ String cidrroute = cidrToIPAndNetmask(route);
+ if (cidrroute == null)
+ return null;
+
+ cidrRoutes.add(cidrroute);
+ }
+ }
+
+ return cidrRoutes;
+ }
+
+ private Collection<String> getCustomRoutesv6(String routes) {
+ Vector<String> cidrRoutes = new Vector<String>();
+ if (routes == null) {
+ // No routes set, return empty vector
+ return cidrRoutes;
+ }
+ for (String route : routes.split("[\n \t]")) {
+ if (!route.equals("")) {
+ cidrRoutes.add(route);
+ }
+ }
+
+ return cidrRoutes;
+ }
+
+ private String cidrToIPAndNetmask(String route) {
+ String[] parts = route.split("/");
+
+ // No /xx, assume /32 as netmask
+ if (parts.length == 1)
+ parts = (route + "/32").split("/");
+
+ if (parts.length != 2)
+ return null;
+ int len;
+ try {
+ len = Integer.parseInt(parts[1]);
+ } catch (NumberFormatException ne) {
+ return null;
+ }
+ if (len < 0 || len > 32)
+ return null;
+
+
+ long nm = 0xffffffffl;
+ nm = (nm << (32 - len)) & 0xffffffffl;
+
+ String netmask = String.format(Locale.ENGLISH, "%d.%d.%d.%d", (nm & 0xff000000) >> 24, (nm & 0xff0000) >> 16, (nm & 0xff00) >> 8, nm & 0xff);
+ return parts[0] + " " + netmask;
+ }
+
+ private String[] buildOpenvpnArgv(File cacheDir) {
+ Vector<String> args = new Vector<String>();
+
+ // Add fixed paramenters
+ //args.add("/data/data/de.blinkt.openvpn/lib/openvpn");
+ args.add(cacheDir.getAbsolutePath() + "/" + VpnProfile.MINIVPN);
+
+ args.add("--config");
+ args.add(cacheDir.getAbsolutePath() + "/" + OVPNCONFIGFILE);
+
+
+ return args.toArray(new String[args.size()]);
+ }
+
+ public Intent prepareIntent(Context context) {
+ String prefix = context.getPackageName();
+
+ Intent intent = new Intent(context, OpenVpnService.class);
+
+ if (mAuthenticationType == VpnProfile.TYPE_KEYSTORE || mAuthenticationType == VpnProfile.TYPE_USERPASS_KEYSTORE) {
+ // if (getKeyStoreCertificates(context) == null)
+ // return null;
+ }
+
+ intent.putExtra(prefix + ".ARGV", buildOpenvpnArgv(context.getCacheDir()));
+ intent.putExtra(prefix + ".profileUUID", mUuid.toString());
+
+ ApplicationInfo info = context.getApplicationInfo();
+ intent.putExtra(prefix + ".nativelib", info.nativeLibraryDir);
+
+ try {
+ FileWriter cfg = new FileWriter(context.getCacheDir().getAbsolutePath() + "/" + OVPNCONFIGFILE);
+ cfg.write(getConfigFile(context, false));
+ cfg.flush();
+ cfg.close();
+ } catch (IOException e) {
+ VpnStatus.logException(e);
+ }
+
+ return intent;
+ }
+
+ public String[] getKeyStoreCertificates(Context context) {
+ return getKeyStoreCertificates(context, 5);
+ }
+
+ public static String getDisplayName(String embeddedFile) {
+ int start = DISPLAYNAME_TAG.length();
+ int end = embeddedFile.indexOf(INLINE_TAG);
+ return embeddedFile.substring(start,end);
+ }
+
+ public static String getEmbeddedContent(String data)
+ {
+ if (!data.contains(INLINE_TAG))
+ return data;
+
+ int start = data.indexOf(INLINE_TAG) + INLINE_TAG.length();
+ return data.substring(start);
+ }
+
+ public static boolean isEmbedded(String data) {
+ if (data==null)
+ return false;
+ if(data.startsWith(INLINE_TAG) || data.startsWith(DISPLAYNAME_TAG))
+ return true;
+ else
+ return false;
+ }
+
+
+ class NoCertReturnedException extends Exception {
+ public NoCertReturnedException (String msg) {
+ super(msg);
+ }
+ }
+
+ synchronized String[] getKeyStoreCertificates(Context context,int tries) {
+ PrivateKey privateKey = null;
+ X509Certificate[] cachain;
+ Exception exp=null;
+ try {
+ privateKey = KeyChain.getPrivateKey(context, mAlias);
+ mPrivateKey = privateKey;
+
+ String keystoreChain = null;
+
+
+ cachain = KeyChain.getCertificateChain(context, mAlias);
+ if(cachain == null)
+ throw new NoCertReturnedException("No certificate returned from Keystore");
+
+ if (cachain.length <= 1 && !nonNull(mCaFilename)) {
+ VpnStatus.logMessage(VpnStatus.LogLevel.ERROR, "", context.getString(R.string.keychain_nocacert));
+ } else {
+ StringWriter ksStringWriter = new StringWriter();
+
+ PemWriter pw = new PemWriter(ksStringWriter);
+ for (int i = 1; i < cachain.length; i++) {
+ X509Certificate cert = cachain[i];
+ pw.writeObject(new PemObject("CERTIFICATE", cert.getEncoded()));
+ }
+ pw.close();
+ keystoreChain = ksStringWriter.toString();
+ }
+
+
+ String caout = null;
+ if (nonNull(mCaFilename)) {
+ try {
+ Certificate cacert = X509Utils.getCertificateFromFile(mCaFilename);
+ StringWriter caoutWriter = new StringWriter();
+ PemWriter pw = new PemWriter(caoutWriter);
+
+ pw.writeObject(new PemObject("CERTIFICATE", cacert.getEncoded()));
+ pw.close();
+ caout= caoutWriter.toString();
+
+ } catch (Exception e) {
+ VpnStatus.logError("Could not read CA certificate" + e.getLocalizedMessage());
+ }
+ }
+
+
+ StringWriter certout = new StringWriter();
+
+
+ if (cachain.length >= 1) {
+ X509Certificate usercert = cachain[0];
+
+ PemWriter upw = new PemWriter(certout);
+ upw.writeObject(new PemObject("CERTIFICATE", usercert.getEncoded()));
+ upw.close();
+
+ }
+ String user = certout.toString();
+
+
+ String ca, extra;
+ if(caout==null) {
+ ca =keystoreChain;
+ extra=null;
+ } else {
+ ca = caout;
+ extra=keystoreChain;
+ }
+
+ return new String[]{ca, extra, user};
+ } catch (InterruptedException e) {
+ exp=e;
+ } catch (FileNotFoundException e) {
+ exp=e;
+ } catch (CertificateException e) {
+ exp=e;
+ } catch (IOException e) {
+ exp=e;
+ } catch (KeyChainException e) {
+ exp=e;
+ } catch (NoCertReturnedException e) {
+ exp =e;
+ } catch (IllegalArgumentException e) {
+ exp =e;
+ } catch (AssertionError e) {
+ if (tries ==0)
+ return null;
+ VpnStatus.logError(String.format("Failure getting Keystore Keys (%s), retrying",e.getLocalizedMessage()));
+ try {
+ Thread.sleep(3000);
+ } catch (InterruptedException e1) {
+ VpnStatus.logException(e1);
+ }
+ return getKeyStoreCertificates(context, tries-1);
+ }
+ if (exp != null) {
+ exp.printStackTrace();
+ VpnStatus.logError(R.string.keyChainAccessError, exp.getLocalizedMessage());
+
+ VpnStatus.logError(R.string.keychain_access);
+ if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) {
+ if (!mAlias.matches("^[a-zA-Z0-9]$")) {
+ VpnStatus.logError(R.string.jelly_keystore_alphanumeric_bug);
+ }
+ }
+ }
+ return null;
+ }
+
+ //! Return an error if somethign is wrong
+ public int checkProfile(Context context) {
+ if (mAuthenticationType == TYPE_KEYSTORE || mAuthenticationType == TYPE_USERPASS_KEYSTORE) {
+ // if (mAlias == null)
+ // return R.string.no_keystore_cert_selected;
+ }
+
+ if (!mUsePull || mAuthenticationType == TYPE_STATICKEYS) {
+ if (mIPv4Address == null || cidrToIPAndNetmask(mIPv4Address) == null)
+ return R.string.ipv4_format_error;
+ }
+ if (!mUseDefaultRoute && (getCustomRoutes(mCustomRoutes) == null || getCustomRoutes(mExcludedRoutes) ==null))
+ return R.string.custom_route_format_error;
+
+ // Everything okay
+ return R.string.no_error_found;
+
+ }
+
+ //! Openvpn asks for a "Private Key", this should be pkcs12 key
+ //
+ public String getPasswordPrivateKey() {
+ if (mTransientPCKS12PW != null) {
+ String pwcopy = mTransientPCKS12PW;
+ mTransientPCKS12PW = null;
+ return pwcopy;
+ }
+ switch (mAuthenticationType) {
+ case TYPE_PKCS12:
+ case TYPE_USERPASS_PKCS12:
+ return mPKCS12Password;
+
+ case TYPE_CERTIFICATES:
+ case TYPE_USERPASS_CERTIFICATES:
+ return mKeyPassword;
+
+ case TYPE_USERPASS:
+ case TYPE_STATICKEYS:
+ default:
+ return null;
+ }
+ }
+
+ public boolean isUserPWAuth() {
+ switch (mAuthenticationType) {
+ case TYPE_USERPASS:
+ case TYPE_USERPASS_CERTIFICATES:
+ case TYPE_USERPASS_KEYSTORE:
+ case TYPE_USERPASS_PKCS12:
+ return true;
+ default:
+ return false;
+
+ }
+ }
+
+ public boolean requireTLSKeyPassword() {
+ if (!nonNull(mClientKeyFilename))
+ return false;
+
+ String data = "";
+ if (isEmbedded(mClientKeyFilename))
+ data = mClientKeyFilename;
+ else {
+ char[] buf = new char[2048];
+ FileReader fr;
+ try {
+ fr = new FileReader(mClientKeyFilename);
+ int len = fr.read(buf);
+ while (len > 0) {
+ data += new String(buf, 0, len);
+ len = fr.read(buf);
+ }
+ fr.close();
+ } catch (FileNotFoundException e) {
+ return false;
+ } catch (IOException e) {
+ return false;
+ }
+
+ }
+
+ if (data.contains("Proc-Type: 4,ENCRYPTED"))
+ return true;
+ else if (data.contains("-----BEGIN ENCRYPTED PRIVATE KEY-----"))
+ return true;
+ else
+ return false;
+ }
+
+ public int needUserPWInput() {
+ if ((mAuthenticationType == TYPE_PKCS12 || mAuthenticationType == TYPE_USERPASS_PKCS12) &&
+ (mPKCS12Password == null || mPKCS12Password.equals(""))) {
+ if (mTransientPCKS12PW == null)
+ return R.string.pkcs12_file_encryption_key;
+ }
+
+ if (mAuthenticationType == TYPE_CERTIFICATES || mAuthenticationType == TYPE_USERPASS_CERTIFICATES) {
+ if (requireTLSKeyPassword() && !nonNull(mKeyPassword))
+ if (mTransientPCKS12PW == null) {
+ return R.string.private_key_password;
+ }
+ }
+
+ if (isUserPWAuth() && !(nonNull(mUsername) && (nonNull(mPassword) || mTransientPW != null))) {
+ return R.string.password;
+ }
+ return 0;
+ }
+
+ public String getPasswordAuth() {
+ if (mTransientPW != null) {
+ String pwcopy = mTransientPW;
+ mTransientPW = null;
+ return pwcopy;
+ } else {
+ return mPassword;
+ }
+ }
+
+ // Used by the Array Adapter
+ @Override
+ public String toString() {
+ return mName;
+ }
+
+ public String getUUIDString() {
+ return mUuid.toString();
+ }
+
+ public PrivateKey getKeystoreKey() {
+ return mPrivateKey;
+ }
+
+ public String getSignedData(String b64data) {
+ PrivateKey privkey = getKeystoreKey();
+ Exception err;
+
+ byte[] data = Base64.decode(b64data, Base64.DEFAULT);
+
+ // The Jelly Bean *evil* Hack
+ // 4.2 implements the RSA/ECB/PKCS1PADDING in the OpenSSLprovider
+ if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) {
+ return processSignJellyBeans(privkey, data);
+ }
+
+
+ try {
+
+
+ Cipher rsasinger = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
+
+ rsasinger.init(Cipher.ENCRYPT_MODE, privkey);
+
+ byte[] signed_bytes = rsasinger.doFinal(data);
+ return Base64.encodeToString(signed_bytes, Base64.NO_WRAP);
+
+ } catch (NoSuchAlgorithmException e) {
+ err = e;
+ } catch (InvalidKeyException e) {
+ err = e;
+ } catch (NoSuchPaddingException e) {
+ err = e;
+ } catch (IllegalBlockSizeException e) {
+ err = e;
+ } catch (BadPaddingException e) {
+ err = e;
+ }
+
+ VpnStatus.logError(R.string.error_rsa_sign, err.getClass().toString(), err.getLocalizedMessage());
+
+ return null;
+
+ }
+
+ private String processSignJellyBeans(PrivateKey privkey, byte[] data) {
+ Exception err;
+ try {
+ Method getKey = privkey.getClass().getSuperclass().getDeclaredMethod("getOpenSSLKey");
+ getKey.setAccessible(true);
+
+ // Real object type is OpenSSLKey
+ Object opensslkey = getKey.invoke(privkey);
+
+ getKey.setAccessible(false);
+
+ Method getPkeyContext = opensslkey.getClass().getDeclaredMethod("getPkeyContext");
+
+ // integer pointer to EVP_pkey
+ getPkeyContext.setAccessible(true);
+ int pkey = (Integer) getPkeyContext.invoke(opensslkey);
+ getPkeyContext.setAccessible(false);
+
+ // 112 with TLS 1.2 (172 back with 4.3), 36 with TLS 1.0
+ byte[] signed_bytes = NativeUtils.rsasign(data, pkey);
+ return Base64.encodeToString(signed_bytes, Base64.NO_WRAP);
+
+ } catch (NoSuchMethodException e) {
+ err = e;
+ } catch (IllegalArgumentException e) {
+ err = e;
+ } catch (IllegalAccessException e) {
+ err = e;
+ } catch (InvocationTargetException e) {
+ err = e;
+ } catch (InvalidKeyException e) {
+ err = e;
+ }
+ VpnStatus.logError(R.string.error_rsa_sign, err.getClass().toString(), err.getLocalizedMessage());
+
+ return null;
+
+ }
+
+
+}
+
+
+
+
diff --git a/app/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java b/app/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java
new file mode 100644
index 00000000..da011c98
--- /dev/null
+++ b/app/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java
@@ -0,0 +1,82 @@
+package de.blinkt.openvpn.activities;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.*;
+import android.os.IBinder;
+
+import se.leap.bitmaskclient.R;
+import de.blinkt.openvpn.core.OpenVpnService;
+import de.blinkt.openvpn.core.ProfileManager;
+
+/**
+ * Created by arne on 13.10.13.
+ */
+public class DisconnectVPN extends Activity implements DialogInterface.OnClickListener{
+ protected OpenVpnService mService;
+
+ private ServiceConnection mConnection = new ServiceConnection() {
+
+
+ @Override
+ public void onServiceConnected(ComponentName className,
+ IBinder service) {
+ // We've bound to LocalService, cast the IBinder and get LocalService instance
+ OpenVpnService.LocalBinder binder = (OpenVpnService.LocalBinder) service;
+ mService = binder.getService();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName arg0) {
+ mService =null;
+ }
+
+ };
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ Intent intent = new Intent(this, OpenVpnService.class);
+ intent.setAction(OpenVpnService.START_SERVICE);
+ bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+ showDisconnectDialog();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ unbindService(mConnection);
+ }
+
+ // if (getIntent() !=null && OpenVpnService.DISCONNECT_VPN.equals(getIntent().getAction()))
+
+ // setIntent(null);
+
+ /*
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ setIntent(intent);
+ }
+ */
+
+ private void showDisconnectDialog() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.title_cancel);
+ builder.setMessage(R.string.cancel_connection_query);
+ builder.setNegativeButton(android.R.string.no, this);
+ builder.setPositiveButton(android.R.string.yes,this);
+
+ builder.show();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ ProfileManager.setConntectedVpnProfileDisconnected(this);
+ if (mService != null && mService.getManagement() != null)
+ mService.getManagement().stopVPN();
+ }
+ finish();
+ }
+}
diff --git a/app/src/main/java/de/blinkt/openvpn/activities/LogWindow.java b/app/src/main/java/de/blinkt/openvpn/activities/LogWindow.java
new file mode 100644
index 00000000..7ed09dd2
--- /dev/null
+++ b/app/src/main/java/de/blinkt/openvpn/activities/LogWindow.java
@@ -0,0 +1,32 @@
+package de.blinkt.openvpn.activities;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.MenuItem;
+
+import se.leap.bitmaskclient.R;
+import de.blinkt.openvpn.fragments.LogFragment;
+
+/**
+ * Created by arne on 13.10.13.
+ */
+public class LogWindow extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.log_window);
+ getActionBar().setDisplayHomeAsUpEnabled(true);
+
+ if (savedInstanceState == null) {
+ getFragmentManager().beginTransaction()
+ .add(R.id.container, new LogFragment())
+ .commit();
+ }
+
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/app/src/main/java/de/blinkt/openvpn/core/CIDRIP.java b/app/src/main/java/de/blinkt/openvpn/core/CIDRIP.java
new file mode 100644
index 00000000..960e7d11
--- /dev/null
+++ b/app/src/main/java/de/blinkt/openvpn/core/CIDRIP.java
@@ -0,0 +1,70 @@
+package de.blinkt.openvpn.core;
+
+import java.util.Locale;
+
+class CIDRIP {
+ String mIp;
+ int len;
+
+
+ public CIDRIP(String ip, String mask) {
+ mIp = ip;
+ long netmask = getInt(mask);
+
+ // Add 33. bit to ensure the loop terminates
+ netmask += 1l << 32;
+
+ int lenZeros = 0;
+ while ((netmask & 0x1) == 0) {
+ lenZeros++;
+ netmask = netmask >> 1;
+ }
+ // Check if rest of netmask is only 1s
+ if (netmask != (0x1ffffffffl >> lenZeros)) {
+ // Asume no CIDR, set /32
+ len = 32;
+ } else {
+ len = 32 - lenZeros;
+ }
+
+ }
+
+ public CIDRIP(String address, int prefix_length) {
+ len = prefix_length;
+ mIp = address;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(Locale.ENGLISH, "%s/%d", mIp, len);
+ }
+
+ public boolean normalise() {
+ long ip = getInt(mIp);
+
+ long newip = ip & (0xffffffffl << (32 - len));
+ if (newip != ip) {
+ mIp = String.format("%d.%d.%d.%d", (newip & 0xff000000) >> 24, (newip & 0xff0000) >> 16, (newip & 0xff00) >> 8, newip & 0xff);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ static long getInt(String ipaddr) {
+ String[] ipt = ipaddr.split("\\.");
+ long ip = 0;
+
+ ip += Long.parseLong(ipt[0]) << 24;
+ ip += Integer.parseInt(ipt[1]) << 16;
+ ip += Integer.parseInt(ipt[2]) << 8;
+ ip += Integer.parseInt(ipt[3]);
+
+ return ip;
+ }
+
+ public long getInt() {
+ return getInt(mIp);
+ }
+
+} \ No newline at end of file
diff --git a/app/src/main/java/se/leap/openvpn/ConfigParser.java b/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java
index df4eae1b..32e5cabb 100644
--- a/app/src/main/java/se/leap/openvpn/ConfigParser.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java
@@ -1,36 +1,57 @@
-package se.leap.openvpn;
+package de.blinkt.openvpn.core;
+
+import de.blinkt.openvpn.VpnProfile;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Vector;
//! Openvpn Config FIle Parser, probably not 100% accurate but close enough
-// And rember, this is valid :)
+// And remember, this is valid :)
// --<foo>
// bar
// </foo>
public class ConfigParser {
+ public static final String CONVERTED_PROFILE = "converted Profile";
private HashMap<String, Vector<Vector<String>>> options = new HashMap<String, Vector<Vector<String>>>();
+ private HashMap<String, Vector<String>> meta = new HashMap<String, Vector<String>>();
+
+
+ private boolean extraRemotesAsCustom=false;
+
+ /*
+ * TODO: We shouldn't be using this method.
+ * We need to figure out how to use just parseConfig, probably removing parseOptions.
+ */
+ public void setDefinition(HashMap<String,Vector<Vector<String>>> args) {
+ options = args;
+ }
+
public void parseConfig(Reader reader) throws IOException, ConfigParseError {
BufferedReader br =new BufferedReader(reader);
- @SuppressWarnings("unused")
- int lineno=0;
-
while (true){
String line = br.readLine();
if(line==null)
break;
- lineno++;
- Vector<String> args = parseline(line);
+
+ // Check for OpenVPN Access Server Meta information
+ if (line.startsWith("# OVPN_ACCESS_SERVER_")) {
+ Vector<String> metaarg = parsemeta(line);
+ meta.put(metaarg.get(0),metaarg);
+ continue;
+ }
+ Vector<String> args = parseline(line);
+
if(args.size() ==0)
continue;
@@ -47,12 +68,18 @@ public class ConfigParser {
options.get(optionname).add(args);
}
}
- public void setDefinition(HashMap<String,Vector<Vector<String>>> args) {
- options = args;
+
+ private Vector<String> parsemeta(String line) {
+ String meta = line.split("#\\sOVPN_ACCESS_SERVER_", 2)[1];
+ String[] parts = meta.split("=",2);
+ Vector<String> rval = new Vector<String>();
+ Collections.addAll(rval, parts);
+ return rval;
+
}
private void checkinlinefile(Vector<String> args, BufferedReader br) throws IOException, ConfigParseError {
- String arg0 = args.get(0);
+ String arg0 = args.get(0).trim();
// CHeck for <foo>
if(arg0.startsWith("<") && arg0.endsWith(">")) {
String argname = arg0.substring(1, arg0.length()-1);
@@ -64,7 +91,7 @@ public class ConfigParser {
if(line==null){
throw new ConfigParseError(String.format("No endtag </%s> for starttag <%s> found",argname,argname));
}
- if(line.equals(endtag))
+ if(line.trim().equals(endtag))
break;
else {
inlinefile+=line;
@@ -222,10 +249,24 @@ public class ConfigParser {
"dhcp-release",
"dhcp-renew",
"dh",
- "management-hold",
- "management",
+ "group",
+ "ip-win32",
+ "management-hold",
+ "management",
+ "management-client",
+ "management-query-remote",
"management-query-passwords",
+ "management-query-proxy",
+ "management-external-key",
+ "management-forget-disconnect",
+ "management-signal",
+ "management-log-cache",
+ "management-up-down",
+ "management-client-user",
+ "management-client-group",
"pause-exit",
+ "plugin",
+ "machine-readable-output",
"persist-key",
"register-dns",
"route-delay",
@@ -239,24 +280,60 @@ public class ConfigParser {
"tmp-dir",
"tun-ipv6",
"topology",
- "win-sys",
- };
-
-
- // This method is far too long
- public VpnProfile convertProfile() throws ConfigParseError{
+ "user",
+ "win-sys",
+
+ };
+
+ final String[][] ignoreOptionsWithArg =
+ {
+ {"setenv", "IV_GUI_VER"},
+ {"setenv", "IV_OPENVPN_GUI_VERSION"}
+ };
+
+ final String[] connectionOptions = {
+ "local",
+ "remote",
+ "float",
+ "port",
+// "connect-retry",
+ "connect-timeout",
+ "connect-retry-max",
+ "link-mtu",
+ "tun-mtu",
+ "tun-mtu-extra",
+ "fragment",
+ "mtu-disc",
+ "local-port",
+ "remote-port",
+ "bind",
+ "nobind",
+ "proto",
+ "http-proxy",
+ "http-proxy-retry",
+ "http-proxy-timeout",
+ "http-proxy-option",
+ "socks-proxy",
+ "socks-proxy-retry",
+ "explicit-exit-notify",
+ "mssfix"
+ };
+
+
+ // This method is far too long
+ @SuppressWarnings("ConstantConditions")
+ public VpnProfile convertProfile() throws ConfigParseError{
boolean noauthtypeset=true;
- VpnProfile np = new VpnProfile("converted Profile");
+ VpnProfile np = new VpnProfile(CONVERTED_PROFILE);
// Pull, client, tls-client
np.clearDefaults();
- // XXX we are always client
- if(/*options.containsKey("client") || options.containsKey("pull")*/ true) {
+ if(options.containsKey("client") || options.containsKey("pull")) {
np.mUsePull=true;
options.remove("pull");
options.remove("client");
}
-
+
Vector<String> secret = getOption("secret", 1, 2);
if(secret!=null)
{
@@ -266,31 +343,55 @@ public class ConfigParser {
np.mTLSAuthFilename=secret.get(1);
if(secret.size()==3)
np.mTLSAuthDirection=secret.get(2);
-
+
}
-
+
Vector<Vector<String>> routes = getAllOption("route", 1, 4);
if(routes!=null) {
String routeopt = "";
- for(Vector<String> route:routes){
+ String routeExcluded = "";
+ for(Vector<String> route:routes){
String netmask = "255.255.255.255";
- if(route.size() >= 3)
+ String gateway = "vpn_gateway";
+
+ if(route.size() >= 3)
netmask = route.get(2);
+ if (route.size() >= 4)
+ gateway = route.get(3);
+
String net = route.get(1);
try {
CIDRIP cidr = new CIDRIP(net, netmask);
- routeopt+=cidr.toString() + " ";
+ if (gateway.equals("net_gateway"))
+ routeExcluded += cidr.toString() + " ";
+ else
+ routeopt+=cidr.toString() + " ";
} catch (ArrayIndexOutOfBoundsException aioob) {
throw new ConfigParseError("Could not parse netmask of route " + netmask);
} catch (NumberFormatException ne) {
+
+
+
+
throw new ConfigParseError("Could not parse netmask of route " + netmask);
}
-
+
}
np.mCustomRoutes=routeopt;
+ np.mExcludedRoutes=routeExcluded;
}
- // Also recognize tls-auth [inline] direction ...
+ Vector<Vector<String>> routesV6 = getAllOption("route-ipv6", 1, 4);
+ if (routesV6!=null) {
+ String customIPv6Routes = "";
+ for (Vector<String> route:routesV6){
+ customIPv6Routes += route.get(1) + " ";
+ }
+
+ np.mCustomRoutesv6 = customIPv6Routes;
+ }
+
+ // Also recognize tls-auth [inline] direction ...
Vector<Vector<String>> tlsauthoptions = getAllOption("tls-auth", 1, 2);
if(tlsauthoptions!=null) {
for(Vector<String> tlsauth:tlsauthoptions) {
@@ -305,24 +406,32 @@ public class ConfigParser {
}
}
}
-
+
Vector<String> direction = getOption("key-direction", 1, 1);
if(direction!=null)
np.mTLSAuthDirection=direction.get(1);
-
- if(getAllOption("redirect-gateway", 0, 5) != null)
- np.mUseDefaultRoute=true;
-
+ Vector<Vector<String>> defgw = getAllOption("redirect-gateway", 0, 5);
+ if(defgw != null)
+ {
+ np.mUseDefaultRoute=true;
+ checkRedirectParameters(np, defgw);
+ }
+
+ Vector<Vector<String>> redirectPrivate = getAllOption("redirect-private",0,5);
+ if (redirectPrivate != null)
+ {
+ checkRedirectParameters(np,redirectPrivate);
+ }
Vector<String> dev =getOption("dev",1,1);
Vector<String> devtype =getOption("dev-type",1,1);
- if( (devtype !=null && devtype.get(1).equals("tun")) ||
- (dev!=null && dev.get(1).startsWith("tun")) ||
- (devtype ==null && dev == null) ) {
- //everything okay
- } else {
- throw new ConfigParseError("Sorry. Only tun mode is supported. See the FAQ for more detail");
+ if ((devtype != null && devtype.get(1).equals("tun")) ||
+ (dev != null && dev.get(1).startsWith("tun")) ||
+ (devtype == null && dev == null)) {
+ //everything okay
+ } else {
+ throw new ConfigParseError("Sorry. Only tun mode is supported. See the FAQ for more detail");
}
@@ -332,20 +441,27 @@ public class ConfigParser {
if(!mode.get(1).equals("p2p"))
throw new ConfigParseError("Invalid mode for --mode specified, need p2p");
}
-
+
Vector<String> port = getOption("port", 1,1);
if(port!=null){
np.mServerPort = port.get(1);
}
-
- Vector<String> proto = getOption("proto", 1,1);
+
+ Vector<String> rport = getOption("rport", 1,1);
+ if(port!=null){
+ np.mServerPort = port.get(1);
+ }
+
+ Vector<String> proto = getOption("proto", 1,1);
if(proto!=null){
- np.mUseUdp=isUdpProto(proto.get(1));;
+ np.mUseUdp=isUdpProto(proto.get(1));
}
// Parse remote config
- Vector<String> remote = getOption("remote",1,3);
- if(remote != null){
+ Vector<Vector<String>> remotes = getAllOption("remote",1,3);
+
+ if(remotes!=null && remotes.size()>=1 ) {
+ Vector<String> remote = remotes.get(0);
switch (remote.size()) {
case 4:
np.mUseUdp=isUdpProto(remote.get(3));
@@ -355,12 +471,8 @@ public class ConfigParser {
np.mServerName = remote.get(1);
}
}
-
- // Parse remote config
- Vector<String> location = getOption("location",0,2);
- if(location != null && location.size() == 2){
- np.mLocation = location.get(1).replace("__", ", ");
- }
+
+
Vector<Vector<String>> dhcpoptions = getAllOption("dhcp-option", 2, 2);
if(dhcpoptions!=null) {
@@ -381,10 +493,15 @@ public class ConfigParser {
Vector<String> ifconfig = getOption("ifconfig", 2, 2);
if(ifconfig!=null) {
- CIDRIP cidr = new CIDRIP(ifconfig.get(1), ifconfig.get(2));
- np.mIPv4Address=cidr.toString();
+ try {
+ CIDRIP cidr = new CIDRIP(ifconfig.get(1), ifconfig.get(2));
+ np.mIPv4Address=cidr.toString();
+ } catch (NumberFormatException nfe) {
+ throw new ConfigParseError("Could not pase ifconfig IP address: " + nfe.getLocalizedMessage());
+ }
+
}
-
+
if(getOption("remote-random-hostname", 0, 0)!=null)
np.mUseRandomHostname=true;
@@ -398,6 +515,11 @@ public class ConfigParser {
if(cipher!=null)
np.mCipher= cipher.get(1);
+ Vector<String> auth = getOption("auth", 1, 1);
+ if(auth!=null)
+ np.mAuth = auth.get(1);
+
+
Vector<String> ca = getOption("ca",1,1);
if(ca!=null){
np.mCaFilename = ca.get(1);
@@ -420,39 +542,65 @@ public class ConfigParser {
noauthtypeset=false;
}
+
+ Vector<String> compatnames = getOption("compat-names",1,2);
+ Vector<String> nonameremapping = getOption("no-name-remapping",1,1);
Vector<String> tlsremote = getOption("tls-remote",1,1);
if(tlsremote!=null){
np.mRemoteCN = tlsremote.get(1);
np.mCheckRemoteCN=true;
+ np.mX509AuthType = VpnProfile.X509_VERIFY_TLSREMOTE;
+
+ if((compatnames!=null && compatnames.size() > 2) ||
+ (nonameremapping!=null))
+ np.mX509AuthType = VpnProfile.X509_VERIFY_TLSREMOTE_COMPAT_NOREMAPPING;
+ }
+
+ Vector<String> verifyx509name = getOption("verify-x509-name",1,2);
+ if(verifyx509name!=null){
+ np.mRemoteCN = verifyx509name.get(1);
+ np.mCheckRemoteCN=true;
+ if(verifyx509name.size()>2) {
+ if (verifyx509name.get(2).equals("name"))
+ np.mX509AuthType=VpnProfile.X509_VERIFY_TLSREMOTE_RDN;
+ else if (verifyx509name.get(2).equals("name-prefix"))
+ np.mX509AuthType=VpnProfile.X509_VERIFY_TLSREMOTE_RDN_PREFIX;
+ else
+ throw new ConfigParseError("Unknown parameter to x509-verify-name: " + verifyx509name.get(2) );
+ } else {
+ np.mX509AuthType = VpnProfile.X509_VERIFY_TLSREMOTE_DN;
+ }
+
}
+
Vector<String> verb = getOption("verb",1,1);
if(verb!=null){
np.mVerb=verb.get(1);
}
-
+
if(getOption("nobind", 0, 0) != null)
np.mNobind=true;
-
+
if(getOption("persist-tun", 0,0) != null)
np.mPersistTun=true;
-
+
Vector<String> connectretry = getOption("connect-retry", 1, 1);
if(connectretry!=null)
np.mConnectRetry =connectretry.get(1);
-
+
Vector<String> connectretrymax = getOption("connect-retry-max", 1, 1);
if(connectretrymax!=null)
np.mConnectRetryMax =connectretrymax.get(1);
-
+
Vector<Vector<String>> remotetls = getAllOption("remote-cert-tls", 1, 1);
if(remotetls!=null)
if(remotetls.get(0).get(1).equals("server"))
np.mExpectTLSCert=true;
else
options.put("remotetls",remotetls);
-
+
Vector<String> authuser = getOption("auth-user-pass",0,1);
if(authuser !=null){
if(noauthtypeset) {
@@ -463,23 +611,51 @@ public class ConfigParser {
np.mAuthenticationType=VpnProfile.TYPE_USERPASS_KEYSTORE;
}
if(authuser.size()>1) {
- // Set option value to password get to get canche to embed later.
+ // Set option value to password get to get cance to embed later.
np.mUsername=null;
np.mPassword=authuser.get(1);
useEmbbedUserAuth(np,authuser.get(1));
}
-
}
-
+ // Parse OpenVPN Access Server extra
+ Vector<String> friendlyname = meta.get("FRIENDLY_NAME");
+ if(friendlyname !=null && friendlyname.size() > 1)
+ np.mName=friendlyname.get(1);
+
+
+ Vector<String> ocusername = meta.get("USERNAME");
+ if(ocusername !=null && ocusername.size() > 1)
+ np.mUsername=ocusername.get(1);
+
// Check the other options
+ if(remotes !=null && remotes.size()>1 && extraRemotesAsCustom) {
+ // first is already added
+ remotes.remove(0);
+ np.mCustomConfigOptions += getOptionStrings(remotes);
+ np.mUseCustomConfig=true;
+ }
checkIgnoreAndInvalidOptions(np);
fixup(np);
return np;
}
+ private void checkRedirectParameters(VpnProfile np, Vector<Vector<String>> defgw) {
+ 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;
+ }
+ }
+
+ public void useExtraRemotesAsCustom(boolean b) {
+ this.extraRemotesAsCustom = b;
+ }
+
private boolean isUdpProto(String proto) throws ConfigParseError {
boolean isudp;
if(proto.equals("udp") || proto.equals("udp6"))
@@ -493,10 +669,10 @@ public class ConfigParser {
throw new ConfigParseError("Unsupported option to --proto " + proto);
return isudp;
}
-
+
static public void useEmbbedUserAuth(VpnProfile np,String inlinedata)
{
- String data = inlinedata.replace(VpnProfile.INLINE_TAG, "");
+ String data = VpnProfile.getEmbeddedContent(inlinedata);
String[] parts = data.split("\n");
if(parts.length >= 2) {
np.mUsername=parts[0];
@@ -513,25 +689,55 @@ public class ConfigParser {
// removing an item which is not in the map is no error
options.remove(option);
+
+
+
if(options.size()> 0) {
- String custom = "# These Options were found in the config file do not map to config settings:\n";
+ np.mCustomConfigOptions += "# These Options were found in the config file do not map to config settings:\n";
for(Vector<Vector<String>> option:options.values()) {
- for(Vector<String> optionsline: option) {
- for (String arg : optionsline)
- custom+= VpnProfile.openVpnEscape(arg) + " ";
- }
- custom+="\n";
+
+ np.mCustomConfigOptions += getOptionStrings(option);
}
- np.mCustomConfigOptions = custom;
np.mUseCustomConfig=true;
}
}
- private void fixup(VpnProfile np) {
+ boolean ignoreThisOption(Vector<String> option) {
+ for (String[] ignoreOption : ignoreOptionsWithArg) {
+
+ if (option.size() < ignoreOption.length)
+ continue;
+
+ boolean ignore = true;
+ for (int i = 0; i < ignoreOption.length; i++) {
+ if (!ignoreOption[i].equals(option.get(i)))
+ ignore = false;
+ }
+ if (ignore)
+ return true;
+
+ }
+ return false;
+ }
+
+ private String getOptionStrings(Vector<Vector<String>> option) {
+ String custom = "";
+ for (Vector<String> optionsline : option) {
+ if (!ignoreThisOption(optionsline)) {
+ for (String arg : optionsline)
+ custom += VpnProfile.openVpnEscape(arg) + " ";
+ custom += "\n";
+ }
+ }
+ return custom;
+ }
+
+
+ private void fixup(VpnProfile np) {
if(np.mRemoteCN.equals(np.mServerName)) {
np.mRemoteCN="";
}
diff --git a/app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java b/app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java
new file mode 100644
index 00000000..18c5f1d9
--- /dev/null
+++ b/app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java
@@ -0,0 +1,243 @@
+package de.blinkt.openvpn.core;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.State;
+import android.preference.PreferenceManager;
+import se.leap.bitmaskclient.R;
+import de.blinkt.openvpn.core.VpnStatus.ByteCountListener;
+
+import java.util.LinkedList;
+
+import static de.blinkt.openvpn.core.OpenVPNManagement.pauseReason;
+
+public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountListener {
+ private int lastNetwork = -1;
+ private OpenVPNManagement mManagement;
+
+ // Window time in s
+ private final int TRAFFIC_WINDOW = 60;
+ // Data traffic limit in bytes
+ private final long TRAFFIC_LIMIT = 64 * 1024;
+
+
+ connectState network = connectState.DISCONNECTED;
+ connectState screen = connectState.SHOULDBECONNECTED;
+ connectState userpause = connectState.SHOULDBECONNECTED;
+
+ private String lastStateMsg = null;
+
+ enum connectState {
+ SHOULDBECONNECTED,
+ PENDINGDISCONNECT,
+ DISCONNECTED
+ }
+
+ static class Datapoint {
+ private Datapoint(long t, long d) {
+ timestamp = t;
+ data = d;
+ }
+
+ long timestamp;
+ long data;
+ }
+
+ LinkedList<Datapoint> trafficdata = new LinkedList<DeviceStateReceiver.Datapoint>();
+
+ @Override
+ public void updateByteCount(long in, long out, long diffIn, long diffOut) {
+ if (screen != connectState.PENDINGDISCONNECT)
+ return;
+
+ long total = diffIn + diffOut;
+ trafficdata.add(new Datapoint(System.currentTimeMillis(), total));
+
+ while (trafficdata.getFirst().timestamp <= (System.currentTimeMillis() - TRAFFIC_WINDOW * 1000)) {
+ trafficdata.removeFirst();
+ }
+
+ long windowtraffic = 0;
+ for (Datapoint dp : trafficdata)
+ windowtraffic += dp.data;
+
+ if (windowtraffic < TRAFFIC_LIMIT) {
+ screen = connectState.DISCONNECTED;
+ VpnStatus.logInfo(R.string.screenoff_pause,
+ OpenVpnService.humanReadableByteCount(TRAFFIC_LIMIT, false), TRAFFIC_WINDOW);
+
+ mManagement.pause(getPauseReason());
+ }
+ }
+
+
+ public void userPause(boolean pause) {
+ if (pause) {
+ userpause = connectState.DISCONNECTED;
+ // Check if we should disconnect
+ mManagement.pause(getPauseReason());
+ } else {
+ boolean wereConnected = shouldBeConnected();
+ userpause = connectState.SHOULDBECONNECTED;
+ if (shouldBeConnected() && !wereConnected)
+ mManagement.resume();
+ else
+ // Update the reason why we currently paused
+ mManagement.pause(getPauseReason());
+ }
+ }
+
+ public DeviceStateReceiver(OpenVPNManagement magnagement) {
+ super();
+ mManagement = magnagement;
+ }
+
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+
+
+ if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
+ networkStateChange(context);
+ } else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
+ boolean screenOffPause = prefs.getBoolean("screenoff", false);
+
+ if (screenOffPause) {
+ if (ProfileManager.getLastConnectedVpn()!=null && !ProfileManager.getLastConnectedVpn().mPersistTun)
+ VpnStatus.logError(R.string.screen_nopersistenttun);
+
+ screen = connectState.PENDINGDISCONNECT;
+ fillTrafficData();
+ if (network == connectState.DISCONNECTED || userpause == connectState.DISCONNECTED)
+ screen = connectState.DISCONNECTED;
+ }
+ } else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) {
+ // Network was disabled because screen off
+ boolean connected = shouldBeConnected();
+ screen = connectState.SHOULDBECONNECTED;
+
+ /* should be connected has changed because the screen is on now, connect the VPN */
+ if (shouldBeConnected() != connected)
+ mManagement.resume();
+ else if (!shouldBeConnected())
+ /*Update the reason why we are still paused */
+ mManagement.pause(getPauseReason());
+
+ }
+ }
+
+
+ private void fillTrafficData() {
+ trafficdata.add(new Datapoint(System.currentTimeMillis(), TRAFFIC_LIMIT));
+ }
+
+
+ public void networkStateChange(Context context) {
+ NetworkInfo networkInfo = getCurrentNetworkInfo(context);
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ boolean sendusr1 = prefs.getBoolean("netchangereconnect", true);
+
+
+ String netstatestring;
+ if (networkInfo == null) {
+ netstatestring = "not connected";
+ } else {
+ String subtype = networkInfo.getSubtypeName();
+ if (subtype == null)
+ subtype = "";
+ String extrainfo = networkInfo.getExtraInfo();
+ if (extrainfo == null)
+ extrainfo = "";
+
+ /*
+ if(networkInfo.getType()==android.net.ConnectivityManager.TYPE_WIFI) {
+ WifiManager wifiMgr = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ WifiInfo wifiinfo = wifiMgr.getConnectionInfo();
+ extrainfo+=wifiinfo.getBSSID();
+
+ subtype += wifiinfo.getNetworkId();
+ }*/
+
+
+ netstatestring = String.format("%2$s %4$s to %1$s %3$s", networkInfo.getTypeName(),
+ networkInfo.getDetailedState(), extrainfo, subtype);
+ }
+
+ if (networkInfo != null && networkInfo.getState() == State.CONNECTED) {
+ int newnet = networkInfo.getType();
+ network = connectState.SHOULDBECONNECTED;
+
+ if (lastNetwork != newnet) {
+ if (screen == connectState.PENDINGDISCONNECT)
+ screen = connectState.DISCONNECTED;
+
+ if (shouldBeConnected()) {
+ if (sendusr1) {
+ if (lastNetwork == -1) {
+ mManagement.resume();
+ } else {
+ mManagement.reconnect();
+ }
+ } else {
+ mManagement.networkChange();
+ }
+ }
+
+
+ lastNetwork = newnet;
+ }
+ } else if (networkInfo == null) {
+ // Not connected, stop openvpn, set last connected network to no network
+ lastNetwork = -1;
+ if (sendusr1) {
+ network = connectState.DISCONNECTED;
+
+ // Set screen state to be disconnected if disconnect pending
+ if (screen == connectState.PENDINGDISCONNECT)
+ screen = connectState.DISCONNECTED;
+
+ mManagement.pause(getPauseReason());
+ }
+ }
+
+
+ if (!netstatestring.equals(lastStateMsg))
+ VpnStatus.logInfo(R.string.netstatus, netstatestring);
+ lastStateMsg = netstatestring;
+
+ }
+
+ public boolean isUserPaused() {
+ return userpause == connectState.DISCONNECTED;
+ }
+
+ private boolean shouldBeConnected() {
+ return (screen == connectState.SHOULDBECONNECTED && userpause == connectState.SHOULDBECONNECTED &&
+ network == connectState.SHOULDBECONNECTED);
+ }
+
+ private pauseReason getPauseReason() {
+ if (userpause == connectState.DISCONNECTED)
+ return pauseReason.userPause;
+
+ if (screen == connectState.DISCONNECTED)
+ return pauseReason.screenOff;
+
+ if (network == connectState.DISCONNECTED)
+ return pauseReason.noNetwork;
+
+ return pauseReason.userPause;
+ }
+
+ private NetworkInfo getCurrentNetworkInfo(Context context) {
+ ConnectivityManager conn = (ConnectivityManager)
+ context.getSystemService(Context.CONNECTIVITY_SERVICE);
+
+ return conn.getActiveNetworkInfo();
+ }
+}
diff --git a/app/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java b/app/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java
new file mode 100644
index 00000000..1daa3433
--- /dev/null
+++ b/app/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java
@@ -0,0 +1,14 @@
+package de.blinkt.openvpn.core;
+
+import android.app.Application;
+
+/**
+ * Created by arne on 28.12.13.
+ */
+public class ICSOpenVPNApplication extends Application {
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ PRNGFixes.apply();
+ }
+}
diff --git a/app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java b/app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java
new file mode 100644
index 00000000..a2c4796d
--- /dev/null
+++ b/app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java
@@ -0,0 +1,13 @@
+package de.blinkt.openvpn.core;
+
+import java.security.InvalidKeyException;
+
+public class NativeUtils {
+ public static native byte[] rsasign(byte[] input,int pkey) throws InvalidKeyException;
+ static native void jniclose(int fdint);
+
+ static {
+ System.loadLibrary("stlport_shared");
+ System.loadLibrary("opvpnutil");
+ }
+}
diff --git a/app/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java b/app/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java
new file mode 100644
index 00000000..990e70d8
--- /dev/null
+++ b/app/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java
@@ -0,0 +1,300 @@
+package de.blinkt.openvpn.core;
+
+import android.os.Build;
+import android.text.TextUtils;
+
+import junit.framework.Assert;
+
+import java.math.BigInteger;
+import java.net.Inet6Address;
+import java.util.*;
+
+import se.leap.bitmaskclient.BuildConfig;
+
+public class NetworkSpace {
+
+
+ static class ipAddress implements Comparable<ipAddress> {
+ private BigInteger netAddress;
+ public int networkMask;
+ private boolean included;
+ private boolean isV4;
+ private BigInteger firstAddress;
+ private BigInteger lastAddress;
+
+
+ @Override
+ public int compareTo(ipAddress another) {
+ int comp = getFirstAddress().compareTo(another.getFirstAddress());
+ if (comp != 0)
+ return comp;
+
+ // bigger mask means smaller address block
+ if (networkMask > another.networkMask)
+ return -1;
+ else if (another.networkMask == networkMask)
+ return 0;
+ else
+ return 1;
+ }
+
+ public ipAddress(CIDRIP ip, boolean include) {
+ included = include;
+ netAddress = BigInteger.valueOf(ip.getInt());
+ networkMask = ip.len;
+ isV4 = true;
+ }
+
+ public ipAddress(Inet6Address address, int mask, boolean include) {
+ networkMask = mask;
+ included = include;
+
+ int s = 128;
+
+ netAddress = BigInteger.ZERO;
+ for (byte b : address.getAddress()) {
+ s -= 16;
+ netAddress = netAddress.add(BigInteger.valueOf(b).shiftLeft(s));
+ }
+ }
+
+ public BigInteger getLastAddress() {
+ if(lastAddress ==null)
+ lastAddress = getMaskedAddress(true);
+ return lastAddress;
+ }
+
+
+ public BigInteger getFirstAddress() {
+ if (firstAddress ==null)
+ firstAddress =getMaskedAddress(false);
+ return firstAddress;
+ }
+
+
+ private BigInteger getMaskedAddress(boolean one) {
+ BigInteger numAddress = netAddress;
+
+ int numBits;
+ if (isV4) {
+ numBits = 32 - networkMask;
+ } else {
+ numBits = 128 - networkMask;
+ }
+
+ for (int i = 0; i < numBits; i++) {
+ if (one)
+ numAddress = numAddress.setBit(i);
+ else
+ numAddress = numAddress.clearBit(i);
+ }
+ return numAddress;
+ }
+
+
+ @Override
+ public String toString() {
+ //String in = included ? "+" : "-";
+ if (isV4)
+ return String.format(Locale.US,"%s/%d", getIPv4Address(), networkMask);
+ else
+ return String.format(Locale.US, "%s/%d", getIPv6Address(), networkMask);
+ }
+
+ ipAddress(BigInteger baseAddress, int mask, boolean included, boolean isV4) {
+ this.netAddress = baseAddress;
+ this.networkMask = mask;
+ this.included = included;
+ this.isV4 = isV4;
+ }
+
+
+ public ipAddress[] split() {
+ ipAddress firsthalf = new ipAddress(getFirstAddress(), networkMask + 1, included, isV4);
+ ipAddress secondhalf = new ipAddress(firsthalf.getLastAddress().add(BigInteger.ONE), networkMask + 1, included, isV4);
+ if (BuildConfig.DEBUG) Assert.assertTrue(secondhalf.getLastAddress().equals(getLastAddress()));
+ return new ipAddress[]{firsthalf, secondhalf};
+ }
+
+ String getIPv4Address() {
+ if (BuildConfig.DEBUG) {
+ Assert.assertTrue (isV4);
+ Assert.assertTrue (netAddress.longValue() <= 0xffffffffl);
+ Assert.assertTrue (netAddress.longValue() >= 0);
+ }
+ long ip = netAddress.longValue();
+ return String.format(Locale.US, "%d.%d.%d.%d", (ip >> 24) % 256, (ip >> 16) % 256, (ip >> 8) % 256, ip % 256);
+ }
+
+ String getIPv6Address() {
+ if (BuildConfig.DEBUG) Assert.assertTrue (!isV4);
+ BigInteger r = netAddress;
+ if (r.longValue() == 0)
+ return "::";
+
+ Vector<String> parts = new Vector<String>();
+ while (r.compareTo(BigInteger.ZERO) == 1) {
+ parts.add(0, String.format(Locale.US, "%x", r.mod(BigInteger.valueOf(256)).longValue()));
+ r = r.shiftRight(16);
+ }
+
+ return TextUtils.join(":", parts);
+ }
+
+ public boolean containsNet(ipAddress network) {
+ return getFirstAddress().compareTo(network.getFirstAddress()) != 1 &&
+ getLastAddress().compareTo(network.getLastAddress()) != -1;
+ }
+ }
+
+
+ TreeSet<ipAddress> mIpAddresses = new TreeSet<ipAddress>();
+
+
+ public Collection<ipAddress> getNetworks(boolean included) {
+ Vector<ipAddress> ips = new Vector<ipAddress>();
+ for (ipAddress ip : mIpAddresses) {
+ if (ip.included == included)
+ ips.add(ip);
+ }
+ return ips;
+ }
+
+ public void clear() {
+ mIpAddresses.clear();
+ }
+
+
+ void addIP(CIDRIP cidrIp, boolean include) {
+
+ mIpAddresses.add(new ipAddress(cidrIp, include));
+ }
+
+ void addIPv6(Inet6Address address, int mask, boolean included) {
+ mIpAddresses.add(new ipAddress(address, mask, included));
+ }
+
+ TreeSet<ipAddress> generateIPList() {
+
+ PriorityQueue<ipAddress> networks = new PriorityQueue<ipAddress>(mIpAddresses);
+
+ TreeSet<ipAddress> ipsDone = new TreeSet<ipAddress>();
+
+ ipAddress currentNet = networks.poll();
+ if (currentNet==null)
+ return ipsDone;
+
+ while (currentNet!=null) {
+ // Check if it and the next of it are compatbile
+ ipAddress nextNet = networks.poll();
+
+ if (BuildConfig.DEBUG) Assert.assertNotNull(currentNet);
+ if (nextNet== null || currentNet.getLastAddress().compareTo(nextNet.getFirstAddress()) == -1) {
+ // Everything good, no overlapping nothing to do
+ ipsDone.add(currentNet);
+
+ currentNet = nextNet;
+ } else {
+ // This network is smaller or equal to the next but has the same base address
+ if (currentNet.getFirstAddress().equals(nextNet.getFirstAddress()) && currentNet.networkMask >= nextNet.networkMask) {
+ if (currentNet.included == nextNet.included) {
+ // Included in the next next and same type
+ // Simply forget our current network
+ currentNet=nextNet;
+ } else {
+ // our currentnet is included in next and types differ. Need to split the next network
+ ipAddress[] newNets = nextNet.split();
+
+ // First add the second half to keep the order in networks
+ if (!networks.contains(newNets[1]))
+ networks.add(newNets[1]);
+
+ if (newNets[0].getLastAddress().equals(currentNet.getLastAddress())) {
+ if (BuildConfig.DEBUG) Assert.assertEquals (newNets[0].networkMask, currentNet.networkMask);
+ // Don't add the lower half that would conflict with currentNet
+ } else {
+ if (!networks.contains(newNets[0]))
+ networks.add(newNets[0]);
+ }
+ // Keep currentNet as is
+ }
+ } else {
+ if (BuildConfig.DEBUG) {
+ Assert.assertTrue(currentNet.networkMask < nextNet.networkMask);
+ Assert.assertTrue (nextNet.getFirstAddress().compareTo(currentNet.getFirstAddress()) == 1);
+ Assert.assertTrue (currentNet.getLastAddress().compareTo(nextNet.getLastAddress()) != -1);
+ }
+ // This network is bigger than the next and last ip of current >= next
+
+ if (currentNet.included == nextNet.included) {
+ // Next network is in included in our network with the same type,
+ // simply ignore the next and move on
+ } else {
+ // We need to split our network
+ ipAddress[] newNets = currentNet.split();
+
+
+ if (newNets[1].networkMask == nextNet.networkMask) {
+ if (BuildConfig.DEBUG) {
+ Assert.assertTrue (newNets[1].getFirstAddress().equals(nextNet.getFirstAddress()));
+ Assert.assertTrue (newNets[1].getLastAddress().equals(currentNet.getLastAddress()));
+ // Splitted second equal the next network, do not add it
+ }
+ networks.add(nextNet);
+ } else {
+ // Add the smaller network first
+ networks.add(newNets[1]);
+ networks.add(nextNet);
+ }
+ currentNet = newNets[0];
+
+ }
+ }
+ }
+
+ }
+
+ return ipsDone;
+ }
+
+ Collection<ipAddress> getPositiveIPList() {
+ TreeSet<ipAddress> ipsSorted = generateIPList();
+
+ Vector<ipAddress> ips = new Vector<ipAddress>();
+ for (ipAddress ia : ipsSorted) {
+ if (ia.included)
+ ips.add(ia);
+ }
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
+ // Include postive routes from the original set under < 4.4 since these might overrule the local
+ // network but only if no smaller negative route exists
+ for(ipAddress origIp: mIpAddresses){
+ if (!origIp.included)
+ continue;
+
+ // The netspace exists
+ if(ipsSorted.contains(origIp))
+ continue;
+
+ boolean skipIp=false;
+ // If there is any smaller net that is excluded we may not add the positive route back
+ for (ipAddress calculatedIp: ipsSorted) {
+ if(!calculatedIp.included && origIp.containsNet(calculatedIp)) {
+ skipIp=true;
+ break;
+ }
+ }
+ if (skipIp)
+ continue;
+
+ // It is safe to include the IP
+ ips.add(origIp);
+ }
+
+ }
+
+ return ips;
+ }
+
+}
diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java
new file mode 100644
index 00000000..a5a3e9f4
--- /dev/null
+++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java
@@ -0,0 +1,24 @@
+package de.blinkt.openvpn.core;
+
+public interface OpenVPNManagement {
+ enum pauseReason {
+ noNetwork,
+ userPause,
+ screenOff
+ }
+
+ int mBytecountInterval =2;
+
+ void reconnect();
+
+ void pause(pauseReason reason);
+
+ void resume();
+
+ boolean stopVPN();
+
+ /*
+ * Rebind the interface
+ */
+ void networkChange();
+}
diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java
new file mode 100644
index 00000000..67c24884
--- /dev/null
+++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java
@@ -0,0 +1,174 @@
+package de.blinkt.openvpn.core;
+
+import android.util.Log;
+import se.leap.bitmaskclient.R;
+import de.blinkt.openvpn.VpnProfile;
+import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus;
+import de.blinkt.openvpn.core.VpnStatus.LogItem;
+
+import java.io.*;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class OpenVPNThread implements Runnable {
+ private static final String DUMP_PATH_STRING = "Dump path: ";
+ private static final String TAG = "OpenVPN";
+ public static final int M_FATAL = (1 << 4);
+ public static final int M_NONFATAL = (1 << 5);
+ public static final int M_WARN = (1 << 6);
+ public static final int M_DEBUG = (1 << 7);
+ private String[] mArgv;
+ private Process mProcess;
+ private String mNativeDir;
+ private OpenVpnService mService;
+ private String mDumpPath;
+ private Map<String, String> mProcessEnv;
+
+ public OpenVPNThread(OpenVpnService service,String[] argv, Map<String,String> processEnv, String nativelibdir)
+ {
+ mArgv = argv;
+ mNativeDir = nativelibdir;
+ mService = service;
+ mProcessEnv = processEnv;
+ }
+
+ public void stopProcess() {
+ mProcess.destroy();
+ }
+
+
+
+ @Override
+ public void run() {
+ try {
+ Log.i(TAG, "Starting openvpn");
+ startOpenVPNThreadArgs(mArgv, mProcessEnv);
+ Log.i(TAG, "Giving up");
+ } catch (Exception e) {
+ VpnStatus.logException("Starting OpenVPN Thread" ,e);
+ Log.e(TAG, "OpenVPNThread Got " + e.toString());
+ } finally {
+ int exitvalue = 0;
+ try {
+ if (mProcess!=null)
+ exitvalue = mProcess.waitFor();
+ } catch ( IllegalThreadStateException ite) {
+ VpnStatus.logError("Illegal Thread state: " + ite.getLocalizedMessage());
+ } catch (InterruptedException ie) {
+ VpnStatus.logError("InterruptedException: " + ie.getLocalizedMessage());
+ }
+ if( exitvalue != 0)
+ VpnStatus.logError("Process exited with exit value " + exitvalue);
+
+ VpnStatus.updateStateString("NOPROCESS", "No process running.", R.string.state_noprocess, ConnectionStatus.LEVEL_NOTCONNECTED);
+ if(mDumpPath!=null) {
+ try {
+ BufferedWriter logout = new BufferedWriter(new FileWriter(mDumpPath + ".log"));
+ SimpleDateFormat timeformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss",Locale.GERMAN);
+ for(LogItem li : VpnStatus.getlogbuffer()){
+ String time = timeformat.format(new Date(li.getLogtime()));
+ logout.write(time +" " + li.getString(mService) + "\n");
+ }
+ logout.close();
+ VpnStatus.logError(R.string.minidump_generated);
+ } catch (IOException e) {
+ VpnStatus.logError("Writing minidump log: " + e.getLocalizedMessage());
+ }
+ }
+
+ mService.processDied();
+ Log.i(TAG, "Exiting");
+ }
+ }
+
+ private void startOpenVPNThreadArgs(String[] argv, Map<String, String> env) {
+ LinkedList<String> argvlist = new LinkedList<String>();
+
+ Collections.addAll(argvlist, argv);
+
+ ProcessBuilder pb = new ProcessBuilder(argvlist);
+ // Hack O rama
+
+ String lbpath = genLibraryPath(argv, pb);
+
+ pb.environment().put("LD_LIBRARY_PATH", lbpath);
+
+ // Add extra variables
+ for(Entry<String,String> e:env.entrySet()){
+ pb.environment().put(e.getKey(), e.getValue());
+ }
+ pb.redirectErrorStream(true);
+ try {
+ mProcess = pb.start();
+ // Close the output, since we don't need it
+ mProcess.getOutputStream().close();
+ InputStream in = mProcess.getInputStream();
+ BufferedReader br = new BufferedReader(new InputStreamReader(in));
+
+ while(true) {
+ String logline = br.readLine();
+ if(logline==null)
+ return;
+
+ if (logline.startsWith(DUMP_PATH_STRING))
+ mDumpPath = logline.substring(DUMP_PATH_STRING.length());
+
+
+ // 1380308330.240114 18000002 Send to HTTP proxy: 'X-Online-Host: bla.blabla.com'
+
+ Pattern p = Pattern.compile("(\\d+).(\\d+) ([0-9a-f])+ (.*)");
+ Matcher m = p.matcher(logline);
+ if(m.matches()) {
+ int flags = Integer.parseInt(m.group(3),16);
+ String msg = m.group(4);
+ int logLevel = flags & 0x0F;
+
+ VpnStatus.LogLevel logStatus = VpnStatus.LogLevel.INFO;
+
+ if ((flags & M_FATAL) != 0)
+ logStatus = VpnStatus.LogLevel.ERROR;
+ else if ((flags & M_NONFATAL)!=0)
+ logStatus = VpnStatus.LogLevel.WARNING;
+ else if ((flags & M_WARN)!=0)
+ logStatus = VpnStatus.LogLevel.WARNING;
+ else if ((flags & M_DEBUG)!=0)
+ logStatus = VpnStatus.LogLevel.VERBOSE;
+
+ if (msg.startsWith("MANAGEMENT: CMD"))
+ logLevel = Math.max(4, logLevel);
+
+
+ VpnStatus.logMessageOpenVPN(logStatus,logLevel,msg);
+ } else {
+ VpnStatus.logInfo("P:" + logline);
+ }
+ }
+
+
+ } catch (IOException e) {
+ VpnStatus.logException("Error reading from output of OpenVPN process" , e);
+ stopProcess();
+ }
+
+
+ }
+
+ private String genLibraryPath(String[] argv, ProcessBuilder pb) {
+ // Hack until I find a good way to get the real library path
+ String applibpath = argv[0].replace("/cache/" + VpnProfile.MINIVPN , "/lib");
+
+ String lbpath = pb.environment().get("LD_LIBRARY_PATH");
+ if(lbpath==null)
+ lbpath = applibpath;
+ else
+ lbpath = lbpath + ":" + applibpath;
+
+ if (!applibpath.equals(mNativeDir)) {
+ lbpath = lbpath + ":" + mNativeDir;
+ }
+ return lbpath;
+ }
+}
diff --git a/app/src/main/java/se/leap/openvpn/OpenVpnManagementThread.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java
index 27a3db65..4cba4f5f 100644
--- a/app/src/main/java/se/leap/openvpn/OpenVpnManagementThread.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java
@@ -1,592 +1,598 @@
-package se.leap.openvpn;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.net.InetSocketAddress;
-import java.net.SocketAddress;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.util.LinkedList;
-import java.util.Vector;
-
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.NoSuchPaddingException;
-
-import se.leap.bitmaskclient.R;
-import android.content.SharedPreferences;
-import android.net.LocalServerSocket;
-import android.net.LocalSocket;
-import android.os.Build;
-import android.os.ParcelFileDescriptor;
-import android.preference.PreferenceManager;
-import android.util.Base64;
-import android.util.Log;
-
-public class OpenVpnManagementThread implements Runnable {
-
- private static final String TAG = "openvpn";
- private LocalSocket mSocket;
- private VpnProfile mProfile;
- private OpenVpnService mOpenVPNService;
- private LinkedList<FileDescriptor> mFDList=new LinkedList<FileDescriptor>();
- private int mBytecountinterval=2;
- private long mLastIn=0;
- private long mLastOut=0;
- private LocalServerSocket mServerSocket;
- private boolean mReleaseHold=true;
- private boolean mWaitingForRelease=false;
- private long mLastHoldRelease=0;
-
- private static Vector<OpenVpnManagementThread> active=new Vector<OpenVpnManagementThread>();
-
- static private native void jniclose(int fdint);
- static private native byte[] rsasign(byte[] input,int pkey) throws InvalidKeyException;
-
- public OpenVpnManagementThread(VpnProfile profile, LocalServerSocket mgmtsocket, OpenVpnService openVpnService) {
- mProfile = profile;
- mServerSocket = mgmtsocket;
- mOpenVPNService = openVpnService;
-
-
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(openVpnService);
- boolean managemeNetworkState = prefs.getBoolean("netchangereconnect", true);
- if(managemeNetworkState)
- mReleaseHold=false;
-
- }
-
- static {
- System.loadLibrary("opvpnutil");
- }
-
- public void managmentCommand(String cmd) {
- if(mSocket!=null) {
- try {
- mSocket.getOutputStream().write(cmd.getBytes());
- mSocket.getOutputStream().flush();
- } catch (IOException e) {
- // Ignore socket stack traces
- }
- }
- }
-
-
- @Override
- public void run() {
- Log.i(TAG, "Managment Socket Thread started");
- byte [] buffer =new byte[2048];
- // mSocket.setSoTimeout(5); // Setting a timeout cannot be that bad
-
- String pendingInput="";
- active.add(this);
-
- try {
- // Wait for a client to connect
- mSocket= mServerSocket.accept();
- InputStream instream = mSocket.getInputStream();
-
- while(true) {
- int numbytesread = instream.read(buffer);
- if(numbytesread==-1)
- return;
-
- FileDescriptor[] fds = null;
- try {
- fds = mSocket.getAncillaryFileDescriptors();
- } catch (IOException e) {
- OpenVPN.logMessage(0, "", "Error reading fds from socket" + e.getLocalizedMessage());
- e.printStackTrace();
- }
- if(fds!=null){
-
- for (FileDescriptor fd : fds) {
-
- mFDList.add(fd);
- }
- }
-
- String input = new String(buffer,0,numbytesread,"UTF-8");
-
- pendingInput += input;
-
- pendingInput=processInput(pendingInput);
-
-
-
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- active.remove(this);
- }
-
- //! Hack O Rama 2000!
- private void protectFileDescriptor(FileDescriptor fd) {
- Exception exp=null;
- try {
- Method getInt = FileDescriptor.class.getDeclaredMethod("getInt$");
- int fdint = (Integer) getInt.invoke(fd);
-
- // You can even get more evil by parsing toString() and extract the int from that :)
-
- mOpenVPNService.protect(fdint);
-
- //ParcelFileDescriptor pfd = ParcelFileDescriptor.fromFd(fdint);
- //pfd.close();
- jniclose(fdint);
- return;
- } catch (NoSuchMethodException e) {
- exp =e;
- } catch (IllegalArgumentException e) {
- exp =e;
- } catch (IllegalAccessException e) {
- exp =e;
- } catch (InvocationTargetException e) {
- exp =e;
- } catch (NullPointerException e) {
- exp =e;
- }
- if(exp!=null) {
- exp.printStackTrace();
- Log.d("Openvpn", "Failed to retrieve fd from socket: " + fd);
- OpenVPN.logMessage(0, "", "Failed to retrieve fd from socket: " + exp.getLocalizedMessage());
- }
- }
-
- private String processInput(String pendingInput) {
-
-
- while(pendingInput.contains("\n")) {
- String[] tokens = pendingInput.split("\\r?\\n", 2);
- processCommand(tokens[0]);
- if(tokens.length == 1)
- // No second part, newline was at the end
- pendingInput="";
- else
- pendingInput=tokens[1];
- }
- return pendingInput;
- }
-
-
- private void processCommand(String command) {
- Log.d(TAG, "processCommand: " + command);
-
- if (command.startsWith(">") && command.contains(":")) {
- String[] parts = command.split(":",2);
- String cmd = parts[0].substring(1);
- String argument = parts[1];
- if(cmd.equals("INFO")) {
- // Ignore greeting from mgmt
- //logStatusMessage(command);
- }else if (cmd.equals("PASSWORD")) {
- processPWCommand(argument);
- } else if (cmd.equals("HOLD")) {
- handleHold();
- } else if (cmd.equals("NEED-OK")) {
- processNeedCommand(argument);
- } else if (cmd.equals("BYTECOUNT")){
- processByteCount(argument);
- } else if (cmd.equals("STATE")) {
- processState(argument);
- } else if (cmd.equals("FATAL")){
- processState(","+cmd+","); //handles FATAL as state
- } else if (cmd.equals("PROXY")) {
- processProxyCMD(argument);
- } else if (cmd.equals("LOG")) {
- String[] args = argument.split(",",3);
- // 0 unix time stamp
- // 1 log level N,I,E etc.
- // 2 log message
- OpenVPN.logMessage(0, "", args[2]);
- } else if (cmd.equals("RSA_SIGN")) {
- processSignCommand(argument);
- } else {
- OpenVPN.logMessage(0, "MGMT:", "Got unrecognized command" + command);
- Log.i(TAG, "Got unrecognized command" + command);
- }
- } else if (command.startsWith("SUCCESS:")) { //Fixes bug LEAP #4565
- if (command.equals("SUCCESS: signal SIGINT thrown")){
- Log.d(TAG, "SUCCESS: signal SIGINT thrown");
- processState(",EXITING,SIGINT,,");
- }
- } else {
- Log.i(TAG, "Got unrecognized line from managment" + command);
- OpenVPN.logMessage(0, "MGMT:", "Got unrecognized line from management:" + command);
- }
- }
- private void handleHold() {
- if(mReleaseHold) {
- releaseHoldCmd();
- } else {
- mWaitingForRelease=true;
- OpenVPN.updateStateString("NONETWORK", "",R.string.state_nonetwork);
- }
- }
- private void releaseHoldCmd() {
- if ((System.currentTimeMillis()- mLastHoldRelease) < 5000) {
- try {
- Thread.sleep(3000);
- } catch (InterruptedException e) {}
-
- }
- mWaitingForRelease=false;
- mLastHoldRelease = System.currentTimeMillis();
- managmentCommand("hold release\n");
- managmentCommand("bytecount " + mBytecountinterval + "\n");
- managmentCommand("state on\n");
- }
-
- public void releaseHold() {
- mReleaseHold=true;
- if(mWaitingForRelease)
- releaseHoldCmd();
-
- }
-
- private void processProxyCMD(String argument) {
- String[] args = argument.split(",",3);
- SocketAddress proxyaddr = ProxyDetection.detectProxy(mProfile);
-
-
- if(args.length >= 2) {
- String proto = args[1];
- if(proto.equals("UDP")) {
- proxyaddr=null;
- }
- }
-
- if(proxyaddr instanceof InetSocketAddress ){
- InetSocketAddress isa = (InetSocketAddress) proxyaddr;
-
- OpenVPN.logInfo(R.string.using_proxy, isa.getHostName(),isa.getPort());
-
- String proxycmd = String.format("proxy HTTP %s %d\n", isa.getHostName(),isa.getPort());
- managmentCommand(proxycmd);
- } else {
- managmentCommand("proxy NONE\n");
- }
-
- }
- private void processState(String argument) {
- String[] args = argument.split(",",3);
- String currentstate = args[1];
- if(args[2].equals(",,")){
- OpenVPN.updateStateString(currentstate,"");
- }
- else if (args[2].endsWith(",,")){ //fixes LEAP Bug #4546
- args[2] = (String) args[2].subSequence(0, args[2].length()-2);
- Log.d(TAG, "processState() STATE: "+ currentstate + " msg: " + args[2]);
- OpenVPN.updateStateString(currentstate,args[2]);
- }
- else{
- OpenVPN.updateStateString(currentstate,args[2]);
- }
- }
-
- private static int repeated_byte_counts = 0;
- private void processByteCount(String argument) {
- // >BYTECOUNT:{BYTES_IN},{BYTES_OUT}
- int comma = argument.indexOf(',');
- long in = Long.parseLong(argument.substring(0, comma));
- long out = Long.parseLong(argument.substring(comma+1));
-
- long diffin = in - mLastIn;
- long diffout = out - mLastOut;
- if(diffin == 0 && diffout == 0)
- repeated_byte_counts++;
- if(repeated_byte_counts > 3)
- Log.d("OpenVPN log", "Repeated byte count = " + repeated_byte_counts);
- mLastIn=in;
- mLastOut=out;
-
- String netstat = String.format("In: %8s, %8s/s Out %8s, %8s/s",
- humanReadableByteCount(in, false),
- humanReadableByteCount(diffin, false),
- humanReadableByteCount(out, false),
- humanReadableByteCount(diffout, false));
- OpenVPN.updateStateString("BYTECOUNT",netstat);
-
-
- }
-
- // From: http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java
- public static String humanReadableByteCount(long bytes, boolean si) {
- int unit = si ? 1000 : 1024;
- if (bytes < unit) return bytes + " B";
- int exp = (int) (Math.log(bytes) / Math.log(unit));
- String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp-1) + (si ? "" : "i");
- return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
- }
-
- private void processNeedCommand(String argument) {
- int p1 =argument.indexOf('\'');
- int p2 = argument.indexOf('\'',p1+1);
-
- String needed = argument.substring(p1+1, p2);
- String extra = argument.split(":",2)[1];
-
- String status = "ok";
-
-
- if (needed.equals("PROTECTFD")) {
- FileDescriptor fdtoprotect = mFDList.pollFirst();
- protectFileDescriptor(fdtoprotect);
- } else if (needed.equals("DNSSERVER")) {
- mOpenVPNService.addDNS(extra);
- }else if (needed.equals("DNSDOMAIN")){
- mOpenVPNService.setDomain(extra);
- } else if (needed.equals("ROUTE")) {
- String[] routeparts = extra.split(" ");
- mOpenVPNService.addRoute(routeparts[0], routeparts[1]);
- } else if (needed.equals("ROUTE6")) {
- mOpenVPNService.addRoutev6(extra);
- } else if (needed.equals("IFCONFIG")) {
- String[] ifconfigparts = extra.split(" ");
- int mtu = Integer.parseInt(ifconfigparts[2]);
- mOpenVPNService.setLocalIP(ifconfigparts[0], ifconfigparts[1],mtu,ifconfigparts[3]);
- } else if (needed.equals("IFCONFIG6")) {
- mOpenVPNService.setLocalIPv6(extra);
-
- } else if (needed.equals("OPENTUN")) {
- if(sendTunFD(needed,extra))
- return;
- else
- status="cancel";
- // This not nice or anything but setFileDescriptors accepts only FilDescriptor class :(
-
- } else {
- Log.e(TAG,"Unkown needok command " + argument);
- return;
- }
-
- String cmd = String.format("needok '%s' %s\n", needed, status);
- managmentCommand(cmd);
- }
-
- private boolean sendTunFD (String needed, String extra) {
- Exception exp = null;
- if(!extra.equals("tun")) {
- // We only support tun
- String errmsg = String.format("Devicetype %s requested, but only tun is possible with the Android API, sorry!",extra);
- OpenVPN.logMessage(0, "", errmsg );
-
- return false;
- }
- ParcelFileDescriptor pfd = mOpenVPNService.openTun();
- if(pfd==null)
- return false;
-
- Method setInt;
- int fdint = pfd.getFd();
- try {
- setInt = FileDescriptor.class.getDeclaredMethod("setInt$",int.class);
- FileDescriptor fdtosend = new FileDescriptor();
-
- setInt.invoke(fdtosend,fdint);
-
- FileDescriptor[] fds = {fdtosend};
- mSocket.setFileDescriptorsForSend(fds);
-
- Log.d("Openvpn", "Sending FD tosocket: " + fdtosend + " " + fdint + " " + pfd);
- // Trigger a send so we can close the fd on our side of the channel
- // The API documentation fails to mention that it will not reset the file descriptor to
- // be send and will happily send the file descriptor on every write ...
- String cmd = String.format("needok '%s' %s\n", needed, "ok");
- managmentCommand(cmd);
-
- // Set the FileDescriptor to null to stop this mad behavior
- mSocket.setFileDescriptorsForSend(null);
-
- pfd.close();
-
- return true;
- } catch (NoSuchMethodException e) {
- exp =e;
- } catch (IllegalArgumentException e) {
- exp =e;
- } catch (IllegalAccessException e) {
- exp =e;
- } catch (InvocationTargetException e) {
- exp =e;
- } catch (IOException e) {
- exp =e;
- }
- if(exp!=null) {
- OpenVPN.logMessage(0,"", "Could not send fd over socket:" + exp.getLocalizedMessage());
- exp.printStackTrace();
- }
- return false;
- }
-
- private void processPWCommand(String argument) {
- //argument has the form Need 'Private Key' password
- // or ">PASSWORD:Verification Failed: '%s' ['%s']"
- String needed;
-
-
-
- try{
-
- int p1 = argument.indexOf('\'');
- int p2 = argument.indexOf('\'',p1+1);
- needed = argument.substring(p1+1, p2);
- if (argument.startsWith("Verification Failed")) {
- proccessPWFailed(needed, argument.substring(p2+1));
- return;
- }
- } catch (StringIndexOutOfBoundsException sioob) {
- OpenVPN.logMessage(0, "", "Could not parse management Password command: " + argument);
- return;
- }
-
- String pw=null;
-
- if(needed.equals("Private Key")) {
- pw = mProfile.getPasswordPrivateKey();
- } else if (needed.equals("Auth")) {
- String usercmd = String.format("username '%s' %s\n",
- needed, VpnProfile.openVpnEscape(mProfile.mUsername));
- managmentCommand(usercmd);
- pw = mProfile.getPasswordAuth();
- }
- if(pw!=null) {
- String cmd = String.format("password '%s' %s\n", needed, VpnProfile.openVpnEscape(pw));
- managmentCommand(cmd);
- } else {
- OpenVPN.logMessage(0, OpenVPN.MANAGMENT_PREFIX, String.format("Openvpn requires Authentication type '%s' but no password/key information available", needed));
- }
-
- }
-
-
-
-
- private void proccessPWFailed(String needed, String args) {
- OpenVPN.updateStateString("AUTH_FAILED", needed + args,R.string.state_auth_failed);
- }
- private void logStatusMessage(String command) {
- OpenVPN.logMessage(0,"MGMT:", command);
- }
-
-
- public static boolean stopOpenVPN() {
- boolean sendCMD=false;
- for (OpenVpnManagementThread mt: active){
- mt.managmentCommand("signal SIGINT\n");
- sendCMD=true;
- try {
- if(mt.mSocket !=null)
- mt.mSocket.close();
- } catch (IOException e) {
- // Ignore close error on already closed socket
- }
- }
- return sendCMD;
- }
-
- public void signalusr1() {
- mReleaseHold=false;
- if(!mWaitingForRelease)
- managmentCommand("signal SIGUSR1\n");
- }
-
- public void reconnect() {
- signalusr1();
- releaseHold();
- }
-
- private void processSignCommand(String b64data) {
-
- PrivateKey privkey = mProfile.getKeystoreKey();
- Exception err =null;
-
- byte[] data = Base64.decode(b64data, Base64.DEFAULT);
-
- // The Jelly Bean *evil* Hack
- // 4.2 implements the RSA/ECB/PKCS1PADDING in the OpenSSLprovider
- if(Build.VERSION.SDK_INT==16){
- processSignJellyBeans(privkey,data);
- return;
- }
-
-
- try{
-
-
- Cipher rsasinger = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
-
- rsasinger.init(Cipher.ENCRYPT_MODE, privkey);
-
- byte[] signed_bytes = rsasinger.doFinal(data);
- String signed_string = Base64.encodeToString(signed_bytes, Base64.NO_WRAP);
- managmentCommand("rsa-sig\n");
- managmentCommand(signed_string);
- managmentCommand("\nEND\n");
- } catch (NoSuchAlgorithmException e){
- err =e;
- } catch (InvalidKeyException e) {
- err =e;
- } catch (NoSuchPaddingException e) {
- err =e;
- } catch (IllegalBlockSizeException e) {
- err =e;
- } catch (BadPaddingException e) {
- err =e;
- }
- if(err !=null) {
- OpenVPN.logError(R.string.error_rsa_sign,err.getClass().toString(),err.getLocalizedMessage());
- }
-
- }
-
-
- private void processSignJellyBeans(PrivateKey privkey, byte[] data) {
- Exception err =null;
- try {
- Method[] allm = privkey.getClass().getSuperclass().getDeclaredMethods();
- System.out.println(allm);
- Method getKey = privkey.getClass().getSuperclass().getDeclaredMethod("getOpenSSLKey");
- getKey.setAccessible(true);
-
- // Real object type is OpenSSLKey
- Object opensslkey = getKey.invoke(privkey);
-
- getKey.setAccessible(false);
-
- Method getPkeyContext = opensslkey.getClass().getDeclaredMethod("getPkeyContext");
-
- // integer pointer to EVP_pkey
- getPkeyContext.setAccessible(true);
- int pkey = (Integer) getPkeyContext.invoke(opensslkey);
- getPkeyContext.setAccessible(false);
-
- byte[] signed_bytes = rsasign(data, pkey);
- String signed_string = Base64.encodeToString(signed_bytes, Base64.NO_WRAP);
- managmentCommand("rsa-sig\n");
- managmentCommand(signed_string);
- managmentCommand("\nEND\n");
-
- } catch (NoSuchMethodException e) {
- err=e;
- } catch (IllegalArgumentException e) {
- err=e;
- } catch (IllegalAccessException e) {
- err=e;
- } catch (InvocationTargetException e) {
- err=e;
- } catch (InvalidKeyException e) {
- err=e;
- }
- if(err !=null) {
- OpenVPN.logError(R.string.error_rsa_sign,err.getClass().toString(),err.getLocalizedMessage());
- }
-
- }
-}
+package de.blinkt.openvpn.core;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.net.LocalServerSocket;
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.os.ParcelFileDescriptor;
+import android.preference.PreferenceManager;
+import android.util.Log;
+
+import org.jetbrains.annotations.NotNull;
+
+import se.leap.bitmaskclient.R;
+import de.blinkt.openvpn.VpnProfile;
+import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.*;
+
+public class OpenVpnManagementThread implements Runnable, OpenVPNManagement {
+
+ private static final String TAG = "openvpn";
+ private LocalSocket mSocket;
+ private VpnProfile mProfile;
+ private OpenVpnService mOpenVPNService;
+ private LinkedList<FileDescriptor> mFDList=new LinkedList<FileDescriptor>();
+ private LocalServerSocket mServerSocket;
+ private boolean mReleaseHold=true;
+ private boolean mWaitingForRelease=false;
+ private long mLastHoldRelease=0;
+
+ private static Vector<OpenVpnManagementThread> active=new Vector<OpenVpnManagementThread>();
+ private LocalSocket mServerSocketLocal;
+
+ private pauseReason lastPauseReason = pauseReason.noNetwork;
+
+ public OpenVpnManagementThread(VpnProfile profile, OpenVpnService openVpnService) {
+ mProfile = profile;
+ mOpenVPNService = openVpnService;
+
+
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(openVpnService);
+ boolean managemeNetworkState = prefs.getBoolean("netchangereconnect", true);
+ if(managemeNetworkState)
+ mReleaseHold=false;
+
+ }
+
+ public boolean openManagementInterface(@NotNull Context c) {
+ // Could take a while to open connection
+ int tries=8;
+
+ String socketName = (c.getCacheDir().getAbsolutePath() + "/" + "mgmtsocket");
+ // The mServerSocketLocal is transferred to the LocalServerSocket, ignore warning
+
+ mServerSocketLocal = new LocalSocket();
+
+ while(tries > 0 && !mServerSocketLocal.isConnected()) {
+ try {
+ mServerSocketLocal.bind(new LocalSocketAddress(socketName,
+ LocalSocketAddress.Namespace.FILESYSTEM));
+ } catch (IOException e) {
+ // wait 300 ms before retrying
+ try { Thread.sleep(300);
+ } catch (InterruptedException e1) {
+ }
+
+ }
+ tries--;
+ }
+
+ try {
+
+ mServerSocket = new LocalServerSocket(mServerSocketLocal.getFileDescriptor());
+ return true;
+ } catch (IOException e) {
+ VpnStatus.logException(e);
+ }
+ return false;
+
+
+ }
+
+ public void managmentCommand(String cmd) {
+ try {
+ if(mSocket!=null && mSocket.getOutputStream() !=null) {
+ mSocket.getOutputStream().write(cmd.getBytes());
+ mSocket.getOutputStream().flush();
+ }
+ }catch (IOException e) {
+ // Ignore socket stack traces
+ }
+ }
+
+
+ @Override
+ public void run() {
+ byte [] buffer =new byte[2048];
+ // mSocket.setSoTimeout(5); // Setting a timeout cannot be that bad
+
+ String pendingInput="";
+ active.add(this);
+
+ try {
+ // Wait for a client to connect
+ mSocket= mServerSocket.accept();
+ InputStream instream = mSocket.getInputStream();
+ // Close the management socket after client connected
+
+ mServerSocket.close();
+ // Closing one of the two sockets also closes the other
+ //mServerSocketLocal.close();
+
+ while(true) {
+ int numbytesread = instream.read(buffer);
+ if(numbytesread==-1)
+ return;
+
+ FileDescriptor[] fds = null;
+ try {
+ fds = mSocket.getAncillaryFileDescriptors();
+ } catch (IOException e) {
+ VpnStatus.logException("Error reading fds from socket", e);
+ }
+ if(fds!=null){
+ Collections.addAll(mFDList, fds);
+ }
+
+ String input = new String(buffer,0,numbytesread,"UTF-8");
+
+ pendingInput += input;
+
+ pendingInput=processInput(pendingInput);
+
+
+
+ }
+ } catch (IOException e) {
+ if (!e.getMessage().equals("socket closed"))
+ VpnStatus.logException(e);
+ }
+ active.remove(this);
+ }
+
+ //! Hack O Rama 2000!
+ private void protectFileDescriptor(FileDescriptor fd) {
+ Exception exp;
+ try {
+ Method getInt = FileDescriptor.class.getDeclaredMethod("getInt$");
+ int fdint = (Integer) getInt.invoke(fd);
+
+ // You can even get more evil by parsing toString() and extract the int from that :)
+
+ boolean result = mOpenVPNService.protect(fdint);
+ if (!result)
+ VpnStatus.logWarning("Could not protect VPN socket");
+
+
+ //ParcelFileDescriptor pfd = ParcelFileDescriptor.fromFd(fdint);
+ //pfd.close();
+ NativeUtils.jniclose(fdint);
+ return;
+ } catch (NoSuchMethodException e) {
+ exp =e;
+ } catch (IllegalArgumentException e) {
+ exp =e;
+ } catch (IllegalAccessException e) {
+ exp =e;
+ } catch (InvocationTargetException e) {
+ exp =e;
+ } catch (NullPointerException e) {
+ exp =e;
+ }
+
+ Log.d("Openvpn", "Failed to retrieve fd from socket: " + fd);
+ VpnStatus.logException("Failed to retrieve fd from socket (" + fd + ")" , exp);
+ }
+
+ private String processInput(String pendingInput) {
+
+
+ while(pendingInput.contains("\n")) {
+ String[] tokens = pendingInput.split("\\r?\\n", 2);
+ processCommand(tokens[0]);
+ if(tokens.length == 1)
+ // No second part, newline was at the end
+ pendingInput="";
+ else
+ pendingInput=tokens[1];
+ }
+ return pendingInput;
+ }
+
+
+ private void processCommand(String command) {
+ //Log.i(TAG, "Line from managment" + command);
+
+
+ if (command.startsWith(">") && command.contains(":")) {
+ String[] parts = command.split(":",2);
+ String cmd = parts[0].substring(1);
+ String argument = parts[1];
+
+
+ if(cmd.equals("INFO")) {
+ /* Ignore greeting from management */
+ return;
+ }else if (cmd.equals("PASSWORD")) {
+ processPWCommand(argument);
+ } else if (cmd.equals("HOLD")) {
+ handleHold();
+ } else if (cmd.equals("NEED-OK")) {
+ processNeedCommand(argument);
+ } else if (cmd.equals("BYTECOUNT")){
+ processByteCount(argument);
+ } else if (cmd.equals("STATE")) {
+ processState(argument);
+ } else if (cmd.equals("PROXY")) {
+ processProxyCMD(argument);
+ } else if (cmd.equals("LOG")) {
+ processLogMessage(argument);
+ } else if (cmd.equals("RSA_SIGN")) {
+ processSignCommand(argument);
+ } else {
+ VpnStatus.logWarning("MGMT: Got unrecognized command" + command);
+ Log.i(TAG, "Got unrecognized command" + command);
+ }
+ } else if (command.startsWith("SUCCESS:")) {
+ /* Ignore this kind of message too */
+ return;
+ } else if (command.startsWith("PROTECTFD: ")) {
+ FileDescriptor fdtoprotect = mFDList.pollFirst();
+ if (fdtoprotect!=null)
+ protectFileDescriptor(fdtoprotect);
+ } else {
+ Log.i(TAG, "Got unrecognized line from managment" + command);
+ VpnStatus.logWarning("MGMT: Got unrecognized line from management:" + command);
+ }
+ }
+
+ private void processLogMessage(String argument) {
+ String[] args = argument.split(",",4);
+ // 0 unix time stamp
+ // 1 log level N,I,E etc.
+ /*
+ (b) zero or more message flags in a single string:
+ I -- informational
+ F -- fatal error
+ N -- non-fatal error
+ W -- warning
+ D -- debug, and
+ */
+ // 2 log message
+
+ Log.d("OpenVPN", argument);
+
+ VpnStatus.LogLevel level;
+ if (args[1].equals("I")) {
+ level = VpnStatus.LogLevel.INFO;
+ } else if (args[1].equals("W")) {
+ level = VpnStatus.LogLevel.WARNING;
+ } else if (args[1].equals("D")) {
+ level = VpnStatus.LogLevel.VERBOSE;
+ } else if (args[1].equals("F")) {
+ level = VpnStatus.LogLevel.ERROR;
+ } else {
+ level = VpnStatus.LogLevel.INFO;
+ }
+
+ int ovpnlevel = Integer.parseInt(args[2]) & 0x0F;
+ String msg = args[3];
+
+ if (msg.startsWith("MANAGEMENT: CMD"))
+ ovpnlevel = Math.max(4, ovpnlevel);
+
+ VpnStatus.logMessageOpenVPN(level,ovpnlevel, msg);
+ }
+
+ private void handleHold() {
+ if(mReleaseHold) {
+ releaseHoldCmd();
+ } else {
+ mWaitingForRelease=true;
+
+ VpnStatus.updateStatePause(lastPauseReason);
+
+
+ }
+ }
+ private void releaseHoldCmd() {
+ if ((System.currentTimeMillis()- mLastHoldRelease) < 5000) {
+ try {
+ Thread.sleep(3000);
+ } catch (InterruptedException e) {
+ }
+
+ }
+ mWaitingForRelease=false;
+ mLastHoldRelease = System.currentTimeMillis();
+ managmentCommand("hold release\n");
+ managmentCommand("bytecount " + mBytecountInterval + "\n");
+ managmentCommand("state on\n");
+ //managmentCommand("log on all\n");
+ }
+
+ public void releaseHold() {
+ mReleaseHold=true;
+ if(mWaitingForRelease)
+ releaseHoldCmd();
+
+ }
+
+ private void processProxyCMD(String argument) {
+ String[] args = argument.split(",",3);
+ SocketAddress proxyaddr = ProxyDetection.detectProxy(mProfile);
+
+
+ if(args.length >= 2) {
+ String proto = args[1];
+ if(proto.equals("UDP")) {
+ proxyaddr=null;
+ }
+ }
+
+ if(proxyaddr instanceof InetSocketAddress ){
+ InetSocketAddress isa = (InetSocketAddress) proxyaddr;
+
+ VpnStatus.logInfo(R.string.using_proxy, isa.getHostName(), isa.getPort());
+
+ String proxycmd = String.format(Locale.ENGLISH,"proxy HTTP %s %d\n", isa.getHostName(),isa.getPort());
+ managmentCommand(proxycmd);
+ } else {
+ managmentCommand("proxy NONE\n");
+ }
+
+ }
+ private void processState(String argument) {
+ String[] args = argument.split(",",3);
+ String currentstate = args[1];
+
+ if(args[2].equals(",,"))
+ VpnStatus.updateStateString(currentstate, "");
+ else
+ VpnStatus.updateStateString(currentstate, args[2]);
+ }
+
+
+ private void processByteCount(String argument) {
+ // >BYTECOUNT:{BYTES_IN},{BYTES_OUT}
+ int comma = argument.indexOf(',');
+ long in = Long.parseLong(argument.substring(0, comma));
+ long out = Long.parseLong(argument.substring(comma+1));
+
+ VpnStatus.updateByteCount(in, out);
+
+ }
+
+
+
+ private void processNeedCommand(String argument) {
+ int p1 =argument.indexOf('\'');
+ int p2 = argument.indexOf('\'',p1+1);
+
+ String needed = argument.substring(p1+1, p2);
+ String extra = argument.split(":",2)[1];
+
+ String status = "ok";
+
+
+ if (needed.equals("PROTECTFD")) {
+ FileDescriptor fdtoprotect = mFDList.pollFirst();
+ protectFileDescriptor(fdtoprotect);
+ } else if (needed.equals("DNSSERVER")) {
+ mOpenVPNService.addDNS(extra);
+ }else if (needed.equals("DNSDOMAIN")){
+ mOpenVPNService.setDomain(extra);
+ } else if (needed.equals("ROUTE")) {
+ String[] routeparts = extra.split(" ");
+
+ /*
+ buf_printf (&out, "%s %s %s dev %s", network, netmask, gateway, rgi->iface);
+ else
+ buf_printf (&out, "%s %s %s", network, netmask, gateway);
+ */
+
+ if(routeparts.length==5) {
+ assert(routeparts[3].equals("dev"));
+ mOpenVPNService.addRoute(routeparts[0], routeparts[1], routeparts[2], routeparts[4]);
+ } else if (routeparts.length >= 3) {
+ mOpenVPNService.addRoute(routeparts[0], routeparts[1], routeparts[2], null);
+ } else {
+ VpnStatus.logError("Unrecognized ROUTE cmd:" + Arrays.toString(routeparts) + " | " + argument);
+ }
+
+ } else if (needed.equals("ROUTE6")) {
+ String[] routeparts = extra.split(" ");
+ mOpenVPNService.addRoutev6(routeparts[0],routeparts[1]);
+ } else if (needed.equals("IFCONFIG")) {
+ String[] ifconfigparts = extra.split(" ");
+ int mtu = Integer.parseInt(ifconfigparts[2]);
+ mOpenVPNService.setLocalIP(ifconfigparts[0], ifconfigparts[1],mtu,ifconfigparts[3]);
+ } else if (needed.equals("IFCONFIG6")) {
+ mOpenVPNService.setLocalIPv6(extra);
+
+ } else if (needed.equals("PERSIST_TUN_ACTION")) {
+ // check if tun cfg stayed the same
+ status = mOpenVPNService.getTunReopenStatus();
+ } else if (needed.equals("OPENTUN")) {
+ if(sendTunFD(needed,extra))
+ return;
+ else
+ status="cancel";
+ // This not nice or anything but setFileDescriptors accepts only FilDescriptor class :(
+
+ } else {
+ Log.e(TAG,"Unkown needok command " + argument);
+ return;
+ }
+
+ String cmd = String.format("needok '%s' %s\n", needed, status);
+ managmentCommand(cmd);
+ }
+
+ private boolean sendTunFD (String needed, String extra) {
+ Exception exp;
+ if(!extra.equals("tun")) {
+ // We only support tun
+ VpnStatus.logError(String.format("Device type %s requested, but only tun is possible with the Android API, sorry!",extra));
+
+ return false;
+ }
+ ParcelFileDescriptor pfd = mOpenVPNService.openTun();
+ if(pfd==null)
+ return false;
+
+ Method setInt;
+ int fdint = pfd.getFd();
+ try {
+ setInt = FileDescriptor.class.getDeclaredMethod("setInt$",int.class);
+ FileDescriptor fdtosend = new FileDescriptor();
+
+ setInt.invoke(fdtosend,fdint);
+
+ FileDescriptor[] fds = {fdtosend};
+ mSocket.setFileDescriptorsForSend(fds);
+
+ // Trigger a send so we can close the fd on our side of the channel
+ // The API documentation fails to mention that it will not reset the file descriptor to
+ // be send and will happily send the file descriptor on every write ...
+ String cmd = String.format("needok '%s' %s\n", needed, "ok");
+ managmentCommand(cmd);
+
+ // Set the FileDescriptor to null to stop this mad behavior
+ mSocket.setFileDescriptorsForSend(null);
+
+ pfd.close();
+
+ return true;
+ } catch (NoSuchMethodException e) {
+ exp =e;
+ } catch (IllegalArgumentException e) {
+ exp =e;
+ } catch (IllegalAccessException e) {
+ exp =e;
+ } catch (InvocationTargetException e) {
+ exp =e;
+ } catch (IOException e) {
+ exp =e;
+ }
+ VpnStatus.logException("Could not send fd over socket" , exp);
+
+ return false;
+ }
+
+ private void processPWCommand(String argument) {
+ //argument has the form Need 'Private Key' password
+ // or ">PASSWORD:Verification Failed: '%s' ['%s']"
+ String needed;
+
+
+
+ try{
+
+ int p1 = argument.indexOf('\'');
+ int p2 = argument.indexOf('\'',p1+1);
+ needed = argument.substring(p1+1, p2);
+ if (argument.startsWith("Verification Failed")) {
+ proccessPWFailed(needed, argument.substring(p2+1));
+ return;
+ }
+ } catch (StringIndexOutOfBoundsException sioob) {
+ VpnStatus.logError("Could not parse management Password command: " + argument);
+ return;
+ }
+
+ String pw=null;
+
+ if(needed.equals("Private Key")) {
+ pw = mProfile.getPasswordPrivateKey();
+ } else if (needed.equals("Auth")) {
+ String usercmd = String.format("username '%s' %s\n",
+ needed, VpnProfile.openVpnEscape(mProfile.mUsername));
+ managmentCommand(usercmd);
+ pw = mProfile.getPasswordAuth();
+ }
+ if(pw!=null) {
+ String cmd = String.format("password '%s' %s\n", needed, VpnProfile.openVpnEscape(pw));
+ managmentCommand(cmd);
+ } else {
+ VpnStatus.logError(String.format("Openvpn requires Authentication type '%s' but no password/key information available", needed));
+ }
+
+ }
+
+
+
+
+ private void proccessPWFailed(String needed, String args) {
+ VpnStatus.updateStateString("AUTH_FAILED", needed + args, R.string.state_auth_failed, ConnectionStatus.LEVEL_AUTH_FAILED);
+ }
+
+
+ private static boolean stopOpenVPN() {
+ boolean sendCMD=false;
+ for (OpenVpnManagementThread mt: active){
+ mt.managmentCommand("signal SIGINT\n");
+ sendCMD=true;
+ try {
+ if(mt.mSocket !=null)
+ mt.mSocket.close();
+ } catch (IOException e) {
+ // Ignore close error on already closed socket
+ }
+ }
+ return sendCMD;
+ }
+
+ @Override
+ public void networkChange() {
+ if(!mWaitingForRelease)
+ managmentCommand("network-change\n");
+ }
+
+ public void signalusr1() {
+ mReleaseHold=false;
+
+ if(!mWaitingForRelease)
+ managmentCommand("signal SIGUSR1\n");
+ else
+ // If signalusr1 is called update the state string
+ // if there is another for stopping
+ VpnStatus.updateStatePause(lastPauseReason);
+ }
+
+ public void reconnect() {
+ signalusr1();
+ releaseHold();
+ }
+
+ private void processSignCommand(String b64data) {
+
+ String signed_string = mProfile.getSignedData(b64data);
+ if(signed_string==null) {
+ managmentCommand("rsa-sig\n");
+ managmentCommand("\nEND\n");
+ stopOpenVPN();
+ return;
+ }
+ managmentCommand("rsa-sig\n");
+ managmentCommand(signed_string);
+ managmentCommand("\nEND\n");
+ }
+
+ @Override
+ public void pause (pauseReason reason) {
+ lastPauseReason = reason;
+ signalusr1();
+ }
+
+ @Override
+ public void resume() {
+ releaseHold();
+ /* Reset the reason why we are disconnected */
+ lastPauseReason = pauseReason.noNetwork;
+ }
+
+ @Override
+ public boolean stopVPN() {
+ return stopOpenVPN();
+ }
+}
diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVpnService.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVpnService.java
new file mode 100644
index 00000000..010cc4f0
--- /dev/null
+++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVpnService.java
@@ -0,0 +1,765 @@
+package de.blinkt.openvpn.core;
+
+import android.Manifest.permission;
+import android.annotation.TargetApi;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.net.ConnectivityManager;
+import android.net.VpnService;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Handler.Callback;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.preference.PreferenceManager;
+import android.text.TextUtils;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Vector;
+
+import se.leap.bitmaskclient.BuildConfig;
+import se.leap.bitmaskclient.R;
+import de.blinkt.openvpn.VpnProfile;
+import de.blinkt.openvpn.activities.DisconnectVPN;
+import de.blinkt.openvpn.activities.LogWindow;
+import de.blinkt.openvpn.core.VpnStatus.ByteCountListener;
+import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus;
+import de.blinkt.openvpn.core.VpnStatus.StateListener;
+
+import static de.blinkt.openvpn.core.NetworkSpace.ipAddress;
+import static de.blinkt.openvpn.core.VpnStatus.ConnectionStatus.LEVEL_CONNECTED;
+import static de.blinkt.openvpn.core.VpnStatus.ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET;
+import static de.blinkt.openvpn.core.VpnStatus.ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT;
+
+public class OpenVpnService extends VpnService implements StateListener, Callback, ByteCountListener {
+ public static final String START_SERVICE = "de.blinkt.openvpn.START_SERVICE";
+ public static final String START_SERVICE_STICKY = "de.blinkt.openvpn.START_SERVICE_STICKY";
+ public static final String ALWAYS_SHOW_NOTIFICATION = "de.blinkt.openvpn.NOTIFICATION_ALWAYS_VISIBLE";
+ public static final String DISCONNECT_VPN = "de.blinkt.openvpn.DISCONNECT_VPN";
+ private static final String PAUSE_VPN = "de.blinkt.openvpn.PAUSE_VPN";
+ private static final String RESUME_VPN = "se.leap.bitmaskclient.RESUME_VPN";
+ private static final int OPENVPN_STATUS = 1;
+ private static boolean mNotificationAlwaysVisible = false;
+ private final Vector<String> mDnslist = new Vector<String>();
+ private final NetworkSpace mRoutes = new NetworkSpace();
+ private final NetworkSpace mRoutesv6 = new NetworkSpace();
+ private final IBinder mBinder = new LocalBinder();
+ private Thread mProcessThread = null;
+ private VpnProfile mProfile;
+ private String mDomain = null;
+ private CIDRIP mLocalIP = null;
+ private int mMtu;
+ private String mLocalIPv6 = null;
+ private DeviceStateReceiver mDeviceStateReceiver;
+ private boolean mDisplayBytecount = false;
+ private boolean mStarting = false;
+ private long mConnecttime;
+ private boolean mOvpn3 = false;
+ private OpenVPNManagement mManagement;
+ private String mLastTunCfg;
+ private String mRemoteGW;
+
+ //TODO We should know if this is running or not without this method
+ public boolean isRunning() {
+ if (mStarting == true || mProcessThread != null)
+ return true;
+ else
+ return false;
+ }
+
+ // From: http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java
+ public static String humanReadableByteCount(long bytes, boolean mbit) {
+ if (mbit)
+ bytes = bytes * 8;
+ int unit = mbit ? 1000 : 1024;
+ if (bytes < unit)
+ return bytes + (mbit ? " bit" : " B");
+
+ int exp = (int) (Math.log(bytes) / Math.log(unit));
+ String pre = (mbit ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (mbit ? "" : "");
+ if (mbit)
+ return String.format(Locale.getDefault(), "%.1f %sbit", bytes / Math.pow(unit, exp), pre);
+ else
+ return String.format(Locale.getDefault(), "%.1f %sB", bytes / Math.pow(unit, exp), pre);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ String action = intent.getAction();
+ if (action != null && action.equals(START_SERVICE))
+ return mBinder;
+ else
+ return super.onBind(intent);
+ }
+
+ @Override
+ public void onRevoke() {
+ mManagement.stopVPN();
+ endVpnService();
+ }
+
+ // Similar to revoke but do not try to stop process
+ public void processDied() {
+ endVpnService();
+ }
+
+ private void endVpnService() {
+ mProcessThread = null;
+ VpnStatus.removeByteCountListener(this);
+ unregisterDeviceStateReceiver();
+ ProfileManager.setConntectedVpnProfileDisconnected(this);
+ if (!mStarting) {
+ stopForeground(!mNotificationAlwaysVisible);
+
+ if (!mNotificationAlwaysVisible) {
+ stopSelf();
+ VpnStatus.removeStateListener(this);
+ }
+ }
+ }
+
+ private void showNotification(String msg, String tickerText, boolean lowpriority, long when, ConnectionStatus status) {
+ String ns = Context.NOTIFICATION_SERVICE;
+ NotificationManager mNotificationManager = (NotificationManager) getSystemService(ns);
+
+
+ int icon = getIconByConnectionStatus(status);
+
+ android.app.Notification.Builder nbuilder = new Notification.Builder(this);
+
+ if (mProfile != null)
+ nbuilder.setContentTitle(getString(R.string.notifcation_title, mProfile.mName));
+ else
+ nbuilder.setContentTitle(getString(R.string.notifcation_title_notconnect));
+
+ nbuilder.setContentText(msg);
+ nbuilder.setOnlyAlertOnce(true);
+ nbuilder.setOngoing(true);
+ nbuilder.setContentIntent(getLogPendingIntent());
+ nbuilder.setSmallIcon(icon);
+
+
+ if (when != 0)
+ nbuilder.setWhen(when);
+
+
+ // Try to set the priority available since API 16 (Jellybean)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
+ jbNotificationExtras(lowpriority, nbuilder);
+
+ if (tickerText != null && !tickerText.equals(""))
+ nbuilder.setTicker(tickerText);
+
+ @SuppressWarnings("deprecation")
+ Notification notification = nbuilder.getNotification();
+
+
+ mNotificationManager.notify(OPENVPN_STATUS, notification);
+ startForeground(OPENVPN_STATUS, notification);
+ }
+
+ private int getIconByConnectionStatus(ConnectionStatus level) {
+ switch (level) {
+ case LEVEL_CONNECTED:
+ return R.drawable.ic_stat_vpn;
+ case LEVEL_AUTH_FAILED:
+ case LEVEL_NONETWORK:
+ case LEVEL_NOTCONNECTED:
+ return R.drawable.ic_stat_vpn_offline;
+ case LEVEL_CONNECTING_NO_SERVER_REPLY_YET:
+ case LEVEL_WAITING_FOR_USER_INPUT:
+ return R.drawable.ic_stat_vpn_outline;
+ case LEVEL_CONNECTING_SERVER_REPLIED:
+ return R.drawable.ic_stat_vpn_empty_halo;
+ case LEVEL_VPNPAUSED:
+ return android.R.drawable.ic_media_pause;
+ case UNKNOWN_LEVEL:
+ default:
+ return R.drawable.ic_stat_vpn;
+
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ private void jbNotificationExtras(boolean lowpriority,
+ android.app.Notification.Builder nbuilder) {
+ try {
+ if (lowpriority) {
+ Method setpriority = nbuilder.getClass().getMethod("setPriority", int.class);
+ // PRIORITY_MIN == -2
+ setpriority.invoke(nbuilder, -2);
+
+ Method setUsesChronometer = nbuilder.getClass().getMethod("setUsesChronometer", boolean.class);
+ setUsesChronometer.invoke(nbuilder, true);
+
+ }
+
+ Intent disconnectVPN = new Intent(this, DisconnectVPN.class);
+ disconnectVPN.setAction(DISCONNECT_VPN);
+ PendingIntent disconnectPendingIntent = PendingIntent.getActivity(this, 0, disconnectVPN, 0);
+
+ nbuilder.addAction(android.R.drawable.ic_menu_close_clear_cancel,
+ getString(R.string.cancel_connection), disconnectPendingIntent);
+
+ Intent pauseVPN = new Intent(this, OpenVpnService.class);
+ if (mDeviceStateReceiver == null || !mDeviceStateReceiver.isUserPaused()) {
+ pauseVPN.setAction(PAUSE_VPN);
+ PendingIntent pauseVPNPending = PendingIntent.getService(this, 0, pauseVPN, 0);
+ nbuilder.addAction(android.R.drawable.ic_media_pause,
+ getString(R.string.pauseVPN), pauseVPNPending);
+
+ } else {
+ pauseVPN.setAction(RESUME_VPN);
+ PendingIntent resumeVPNPending = PendingIntent.getService(this, 0, pauseVPN, 0);
+ nbuilder.addAction(android.R.drawable.ic_media_play,
+ getString(R.string.resumevpn), resumeVPNPending);
+ }
+
+
+ //ignore exception
+ } catch (NoSuchMethodException nsm) {
+ VpnStatus.logException(nsm);
+ } catch (IllegalArgumentException e) {
+ VpnStatus.logException(e);
+ } catch (IllegalAccessException e) {
+ VpnStatus.logException(e);
+ } catch (InvocationTargetException e) {
+ VpnStatus.logException(e);
+ }
+
+ }
+
+ PendingIntent getLogPendingIntent() {
+ // Let the configure Button show the Log
+ Intent intent = new Intent(getBaseContext(), se.leap.bitmaskclient.Dashboard.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+ PendingIntent startLW = PendingIntent.getActivity(this, 0, intent, 0);
+ intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+ return startLW;
+
+ }
+
+ synchronized void registerDeviceStateReceiver(OpenVPNManagement magnagement) {
+ // Registers BroadcastReceiver to track network connection changes.
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ filter.addAction(Intent.ACTION_SCREEN_ON);
+ mDeviceStateReceiver = new DeviceStateReceiver(magnagement);
+ registerReceiver(mDeviceStateReceiver, filter);
+ VpnStatus.addByteCountListener(mDeviceStateReceiver);
+ }
+
+ synchronized void unregisterDeviceStateReceiver() {
+ if (mDeviceStateReceiver != null)
+ try {
+ VpnStatus.removeByteCountListener(mDeviceStateReceiver);
+ this.unregisterReceiver(mDeviceStateReceiver);
+ } catch (IllegalArgumentException iae) {
+ // I don't know why this happens:
+ // java.lang.IllegalArgumentException: Receiver not registered: de.blinkt.openvpn.NetworkSateReceiver@41a61a10
+ // Ignore for now ...
+ iae.printStackTrace();
+ }
+ mDeviceStateReceiver = null;
+ }
+
+ public void userPause(boolean shouldBePaused) {
+ if (mDeviceStateReceiver != null)
+ mDeviceStateReceiver.userPause(shouldBePaused);
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+
+ if (intent != null && intent.getBooleanExtra(ALWAYS_SHOW_NOTIFICATION, false))
+ mNotificationAlwaysVisible = true;
+
+ VpnStatus.addStateListener(this);
+ VpnStatus.addByteCountListener(this);
+
+ if (intent != null && PAUSE_VPN.equals(intent.getAction())) {
+ if (mDeviceStateReceiver != null)
+ mDeviceStateReceiver.userPause(true);
+ return START_NOT_STICKY;
+ }
+
+ if (intent != null && RESUME_VPN.equals(intent.getAction())) {
+ if (mDeviceStateReceiver != null)
+ mDeviceStateReceiver.userPause(false);
+ return START_NOT_STICKY;
+ }
+
+
+ if (intent != null && START_SERVICE.equals(intent.getAction()))
+ return START_NOT_STICKY;
+ if (intent != null && START_SERVICE_STICKY.equals(intent.getAction())) {
+ return START_REDELIVER_INTENT;
+ }
+
+ if (intent == null)
+ return START_NOT_STICKY;
+
+ // Extract information from the intent.
+ String prefix = getPackageName();
+ String[] argv = intent.getStringArrayExtra(prefix + ".ARGV");
+ String nativelibdir = intent.getStringExtra(prefix + ".nativelib");
+ String profileUUID = intent.getStringExtra(prefix + ".profileUUID");
+
+ mProfile = ProfileManager.get(this, profileUUID);
+
+ String startTitle = getString(R.string.start_vpn_title, mProfile.mName);
+ String startTicker = getString(R.string.start_vpn_ticker, mProfile.mName);
+ showNotification(startTitle, startTicker,
+ false, 0, LEVEL_CONNECTING_NO_SERVER_REPLY_YET);
+
+ // Set a flag that we are starting a new VPN
+ mStarting = true;
+ // Stop the previous session by interrupting the thread.
+ if (mManagement != null && mManagement.stopVPN())
+ // an old was asked to exit, wait 1s
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ //ignore
+ }
+
+
+ if (mProcessThread != null) {
+ mProcessThread.interrupt();
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ //ignore
+ }
+ }
+ // An old running VPN should now be exited
+ mStarting = false;
+
+ // Start a new session by creating a new thread.
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+
+ mOvpn3 = prefs.getBoolean("ovpn3", false);
+ if (!"ovpn3".equals(BuildConfig.FLAVOR))
+ mOvpn3 = false;
+
+
+ // Open the Management Interface
+ if (!mOvpn3) {
+
+ // start a Thread that handles incoming messages of the managment socket
+ OpenVpnManagementThread ovpnManagementThread = new OpenVpnManagementThread(mProfile, this);
+ if (ovpnManagementThread.openManagementInterface(this)) {
+
+ Thread mSocketManagerThread = new Thread(ovpnManagementThread, "OpenVPNManagementThread");
+ mSocketManagerThread.start();
+ mManagement = ovpnManagementThread;
+ VpnStatus.logInfo("started Socket Thread");
+ } else {
+ return START_NOT_STICKY;
+ }
+ }
+
+
+ Runnable processThread;
+ if (mOvpn3) {
+
+ OpenVPNManagement mOpenVPN3 = instantiateOpenVPN3Core();
+ processThread = (Runnable) mOpenVPN3;
+ mManagement = mOpenVPN3;
+
+
+ } else {
+ HashMap<String, String> env = new HashMap<String, String>();
+ processThread = new OpenVPNThread(this, argv, env, nativelibdir);
+ }
+
+ mProcessThread = new Thread(processThread, "OpenVPNProcessThread");
+ mProcessThread.start();
+
+ if (mDeviceStateReceiver != null)
+ unregisterDeviceStateReceiver();
+
+ registerDeviceStateReceiver(mManagement);
+
+
+ ProfileManager.setConnectedVpnProfile(this, mProfile);
+
+ return START_NOT_STICKY;
+ }
+
+ private OpenVPNManagement instantiateOpenVPN3Core() {
+ try {
+ Class cl = Class.forName("de.blinkt.openvpn.core.OpenVPNThreadv3");
+ return (OpenVPNManagement) cl.getConstructor(OpenVpnService.class,VpnProfile.class).newInstance(this,mProfile);
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ } catch (InstantiationException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ e.printStackTrace();
+ } catch (NoSuchMethodException e) {
+ e.printStackTrace();
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ @Override
+ public void onDestroy() {
+ if (mProcessThread != null) {
+ mManagement.stopVPN();
+
+ mProcessThread.interrupt();
+ }
+ if (mDeviceStateReceiver != null) {
+ this.unregisterReceiver(mDeviceStateReceiver);
+ }
+ // Just in case unregister for state
+ VpnStatus.removeStateListener(this);
+
+ }
+
+ private String getTunConfigString() {
+ // The format of the string is not important, only that
+ // two identical configurations produce the same result
+ String cfg = "TUNCFG UNQIUE STRING ips:";
+
+ if (mLocalIP != null)
+ cfg += mLocalIP.toString();
+ if (mLocalIPv6 != null)
+ cfg += mLocalIPv6;
+
+ cfg += "routes: " + TextUtils.join("|", mRoutes.getNetworks(true)) + TextUtils.join("|", mRoutesv6.getNetworks(true));
+ cfg += "excl. routes:" + TextUtils.join("|", mRoutes.getNetworks(false)) + TextUtils.join("|", mRoutesv6.getNetworks(false));
+ cfg += "dns: " + TextUtils.join("|", mDnslist);
+ cfg += "domain: " + mDomain;
+ cfg += "mtu: " + mMtu;
+ return cfg;
+ }
+
+ public ParcelFileDescriptor openTun() {
+
+ //Debug.startMethodTracing(getExternalFilesDir(null).toString() + "/opentun.trace", 40* 1024 * 1024);
+
+ Builder builder = new Builder();
+
+ VpnStatus.logInfo(R.string.last_openvpn_tun_config);
+
+
+ if (mLocalIP == null && mLocalIPv6 == null) {
+ VpnStatus.logError(getString(R.string.opentun_no_ipaddr));
+ return null;
+ }
+
+ if (mLocalIP != null) {
+ try {
+ builder.addAddress(mLocalIP.mIp, mLocalIP.len);
+ } catch (IllegalArgumentException iae) {
+ VpnStatus.logError(R.string.dns_add_error, mLocalIP, iae.getLocalizedMessage());
+ return null;
+ }
+ }
+
+ if (mLocalIPv6 != null) {
+ String[] ipv6parts = mLocalIPv6.split("/");
+ try {
+ builder.addAddress(ipv6parts[0], Integer.parseInt(ipv6parts[1]));
+ } catch (IllegalArgumentException iae) {
+ VpnStatus.logError(R.string.ip_add_error, mLocalIPv6, iae.getLocalizedMessage());
+ return null;
+ }
+
+ }
+
+
+ for (String dns : mDnslist) {
+ try {
+ builder.addDnsServer(dns);
+ } catch (IllegalArgumentException iae) {
+ VpnStatus.logError(R.string.dns_add_error, dns, iae.getLocalizedMessage());
+ }
+ }
+
+
+ builder.setMtu(mMtu);
+
+ Collection<ipAddress> positiveIPv4Routes = mRoutes.getPositiveIPList();
+ Collection<ipAddress> positiveIPv6Routes = mRoutesv6.getPositiveIPList();
+
+ for (NetworkSpace.ipAddress route : positiveIPv4Routes) {
+ try {
+ builder.addRoute(route.getIPv4Address(), route.networkMask);
+ } catch (IllegalArgumentException ia) {
+ VpnStatus.logError(getString(R.string.route_rejected) + route + " " + ia.getLocalizedMessage());
+ }
+ }
+
+ for (NetworkSpace.ipAddress route6 : positiveIPv6Routes) {
+ try {
+ builder.addRoute(route6.getIPv6Address(), route6.networkMask);
+ } catch (IllegalArgumentException ia) {
+ VpnStatus.logError(getString(R.string.route_rejected) + route6 + " " + ia.getLocalizedMessage());
+ }
+ }
+
+ if (mDomain != null)
+ builder.addSearchDomain(mDomain);
+
+ VpnStatus.logInfo(R.string.local_ip_info, mLocalIP.mIp, mLocalIP.len, mLocalIPv6, mMtu);
+ VpnStatus.logInfo(R.string.dns_server_info, TextUtils.join(", ", mDnslist), mDomain);
+ VpnStatus.logInfo(R.string.routes_info_incl, TextUtils.join(", ", mRoutes.getNetworks(true)), TextUtils.join(", ", mRoutesv6.getNetworks(true)));
+ VpnStatus.logInfo(R.string.routes_info_excl, TextUtils.join(", ", mRoutes.getNetworks(false)),TextUtils.join(", ", mRoutesv6.getNetworks(false)));
+ VpnStatus.logDebug(R.string.routes_debug, TextUtils.join(", ", positiveIPv4Routes), TextUtils.join(", ", positiveIPv6Routes));
+
+ String session = mProfile.mName;
+ if (mLocalIP != null && mLocalIPv6 != null)
+ session = getString(R.string.session_ipv6string, session, mLocalIP, mLocalIPv6);
+ else if (mLocalIP != null)
+ session = getString(R.string.session_ipv4string, session, mLocalIP);
+
+ builder.setSession(session);
+
+ // No DNS Server, log a warning
+ if (mDnslist.size() == 0)
+ VpnStatus.logInfo(R.string.warn_no_dns);
+
+ mLastTunCfg = getTunConfigString();
+
+ // Reset information
+ mDnslist.clear();
+ mRoutes.clear();
+ mRoutesv6.clear();
+ mLocalIP = null;
+ mLocalIPv6 = null;
+ mDomain = null;
+
+ builder.setConfigureIntent(getLogPendingIntent());
+
+ try {
+ //Debug.stopMethodTracing();
+ return builder.establish();
+ } catch (Exception e) {
+ VpnStatus.logError(R.string.tun_open_error);
+ VpnStatus.logError(getString(R.string.error) + e.getLocalizedMessage());
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ VpnStatus.logError(R.string.tun_error_helpful);
+ }
+ return null;
+ }
+
+ }
+
+ public void addDNS(String dns) {
+ mDnslist.add(dns);
+ }
+
+ public void setDomain(String domain) {
+ if (mDomain == null) {
+ mDomain = domain;
+ }
+ }
+
+ /** Route that is always included, used by the v3 core */
+ public void addRoute (CIDRIP route) {
+ mRoutes.addIP(route, true);
+ }
+
+ public void addRoute (String dest, String mask, String gateway, String device) {
+ CIDRIP route = new CIDRIP(dest, mask);
+ boolean include = isAndroidTunDevice(device);
+
+ NetworkSpace.ipAddress gatewayIP = new NetworkSpace.ipAddress(new CIDRIP(gateway, 32),false);
+
+ if (mLocalIP==null) {
+ VpnStatus.logError("Local IP address unset but adding route?! This is broken! Please contact author with log");
+ return;
+ }
+ NetworkSpace.ipAddress localNet = new NetworkSpace.ipAddress(mLocalIP,true);
+ if (localNet.containsNet(gatewayIP))
+ include=true;
+
+ if (gateway!= null &&
+ (gateway.equals("255.255.255.255") || gateway.equals(mRemoteGW)))
+ include=true;
+
+
+ if (route.len == 32 && !mask.equals("255.255.255.255")) {
+ VpnStatus.logWarning(R.string.route_not_cidr, dest, mask);
+ }
+
+ if (route.normalise())
+ VpnStatus.logWarning(R.string.route_not_netip, dest, route.len, route.mIp);
+
+ mRoutes.addIP(route, include);
+ }
+
+ public void addRoutev6(String network, String device) {
+ String[] v6parts = network.split("/");
+ boolean included = isAndroidTunDevice(device);
+
+ // Tun is opened after ROUTE6, no device name may be present
+
+ try {
+ Inet6Address ip = (Inet6Address) InetAddress.getAllByName(v6parts[0])[0];
+ int mask = Integer.parseInt(v6parts[1]);
+ mRoutesv6.addIPv6(ip, mask, included);
+
+ } catch (UnknownHostException e) {
+ VpnStatus.logException(e);
+ }
+
+
+ }
+
+ private boolean isAndroidTunDevice(String device) {
+ return device!=null &&
+ (device.startsWith("tun") || "(null)".equals(device) || "vpnservice-tun".equals(device));
+ }
+
+ public void setMtu(int mtu) {
+ mMtu = mtu;
+ }
+
+ public void setLocalIP(CIDRIP cdrip) {
+ mLocalIP = cdrip;
+ }
+
+ public void setLocalIP(String local, String netmask, int mtu, String mode) {
+ mLocalIP = new CIDRIP(local, netmask);
+ mMtu = mtu;
+ mRemoteGW=null;
+
+
+ if (mLocalIP.len == 32 && !netmask.equals("255.255.255.255")) {
+ // get the netmask as IP
+ long netMaskAsInt = CIDRIP.getInt(netmask);
+
+ int masklen;
+ if ("net30".equals(mode))
+ masklen = 30;
+ else
+ masklen = 31;
+
+ int mask = ~( 1 << (32 - (mLocalIP.len +1)));
+ // Netmask is Ip address +/-1, assume net30/p2p with small net
+ if ((netMaskAsInt & mask) == (mLocalIP.getInt() & mask )) {
+ mLocalIP.len = masklen;
+ } else {
+ if (!"p2p".equals(mode))
+ VpnStatus.logWarning(R.string.ip_not_cidr, local, netmask, mode);
+ mRemoteGW=netmask;
+ }
+ }
+ }
+
+ public void setLocalIPv6(String ipv6addr) {
+ mLocalIPv6 = ipv6addr;
+ }
+
+ @Override
+ public void updateState(String state, String logmessage, int resid, ConnectionStatus level) {
+ // If the process is not running, ignore any state,
+ // Notification should be invisible in this state
+ doSendBroadcast(state, level);
+ if (mProcessThread == null && !mNotificationAlwaysVisible)
+ return;
+
+ boolean lowpriority = false;
+ // Display byte count only after being connected
+
+ {
+ if (level == LEVEL_WAITING_FOR_USER_INPUT) {
+ // The user is presented a dialog of some kind, no need to inform the user
+ // with a notifcation
+ return;
+ } else if (level == LEVEL_CONNECTED) {
+ mDisplayBytecount = true;
+ mConnecttime = System.currentTimeMillis();
+ lowpriority = true;
+ } else {
+ mDisplayBytecount = false;
+ }
+
+ // Other notifications are shown,
+ // This also mean we are no longer connected, ignore bytecount messages until next
+ // CONNECTED
+ // Does not work :(
+ String msg = getString(resid);
+ String ticker = msg;
+ showNotification(msg + " " + logmessage, ticker, lowpriority , 0, level);
+
+ }
+ }
+
+ private void doSendBroadcast(String state, ConnectionStatus level) {
+ Intent vpnstatus = new Intent();
+ vpnstatus.setAction("de.blinkt.openvpn.VPN_STATUS");
+ vpnstatus.putExtra("status", level.toString());
+ vpnstatus.putExtra("detailstatus", state);
+ sendBroadcast(vpnstatus, permission.ACCESS_NETWORK_STATE);
+ }
+
+ @Override
+ public void updateByteCount(long in, long out, long diffIn, long diffOut) {
+ if (mDisplayBytecount) {
+ String netstat = String.format(getString(R.string.statusline_bytecount),
+ humanReadableByteCount(in, false),
+ humanReadableByteCount(diffIn / OpenVPNManagement.mBytecountInterval, true),
+ humanReadableByteCount(out, false),
+ humanReadableByteCount(diffOut / OpenVPNManagement.mBytecountInterval, true));
+
+ boolean lowpriority = !mNotificationAlwaysVisible;
+ showNotification(netstat, null, lowpriority, mConnecttime, LEVEL_CONNECTED);
+ }
+
+ }
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ Runnable r = msg.getCallback();
+ if (r != null) {
+ r.run();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public OpenVPNManagement getManagement() {
+ return mManagement;
+ }
+
+ public String getTunReopenStatus() {
+ String currentConfiguration = getTunConfigString();
+ if (currentConfiguration.equals(mLastTunCfg))
+ return "NOACTION";
+ else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
+ return "OPEN_AFTER_CLOSE";
+ else
+ return "OPEN_BEFORE_CLOSE";
+ }
+
+ public class LocalBinder extends Binder {
+ public OpenVpnService getService() {
+ // Return this instance of LocalService so clients can call public methods
+ return OpenVpnService.this;
+ }
+ }
+}
diff --git a/app/src/main/java/de/blinkt/openvpn/core/PRNGFixes.java b/app/src/main/java/de/blinkt/openvpn/core/PRNGFixes.java
new file mode 100644
index 00000000..dd420371
--- /dev/null
+++ b/app/src/main/java/de/blinkt/openvpn/core/PRNGFixes.java
@@ -0,0 +1,334 @@
+package de.blinkt.openvpn.core;/*
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will Google be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, as long as the origin is not misrepresented.
+ */
+
+import android.os.Build;
+import android.os.Process;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.security.SecureRandomSpi;
+import java.security.Security;
+
+/**
+ * Fixes for the output of the default PRNG having low entropy.
+ *
+ * The fixes need to be applied via {@link #apply()} before any use of Java
+ * Cryptography Architecture primitives. A good place to invoke them is in the
+ * application's {@code onCreate}.
+ */
+public final class PRNGFixes {
+
+ private static final int VERSION_CODE_JELLY_BEAN = 16;
+ private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18;
+ private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL =
+ getBuildFingerprintAndDeviceSerial();
+
+ /** Hidden constructor to prevent instantiation. */
+ private PRNGFixes() {}
+
+ /**
+ * Applies all fixes.
+ *
+ * @throws SecurityException if a fix is needed but could not be applied.
+ */
+ public static void apply() {
+ applyOpenSSLFix();
+ installLinuxPRNGSecureRandom();
+ }
+
+ /**
+ * Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the
+ * fix is not needed.
+ *
+ * @throws SecurityException if the fix is needed but could not be applied.
+ */
+ private static void applyOpenSSLFix() throws SecurityException {
+ if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN)
+ || (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) {
+ // No need to apply the fix
+ return;
+ }
+
+ try {
+ // Mix in the device- and invocation-specific seed.
+ Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
+ .getMethod("RAND_seed", byte[].class)
+ .invoke(null, generateSeed());
+
+ // Mix output of Linux PRNG into OpenSSL's PRNG
+ int bytesRead = (Integer) Class.forName(
+ "org.apache.harmony.xnet.provider.jsse.NativeCrypto")
+ .getMethod("RAND_load_file", String.class, long.class)
+ .invoke(null, "/dev/urandom", 1024);
+ if (bytesRead != 1024) {
+ throw new IOException(
+ "Unexpected number of bytes read from Linux PRNG: "
+ + bytesRead);
+ }
+ } catch (Exception e) {
+ throw new SecurityException("Failed to seed OpenSSL PRNG", e);
+ }
+ }
+
+ /**
+ * Installs a Linux PRNG-backed {@code SecureRandom} implementation as the
+ * default. Does nothing if the implementation is already the default or if
+ * there is not need to install the implementation.
+ *
+ * @throws SecurityException if the fix is needed but could not be applied.
+ */
+ private static void installLinuxPRNGSecureRandom()
+ throws SecurityException {
+ if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) {
+ // No need to apply the fix
+ return;
+ }
+
+ // Install a Linux PRNG-based SecureRandom implementation as the
+ // default, if not yet installed.
+ Provider[] secureRandomProviders =
+ Security.getProviders("SecureRandom.SHA1PRNG");
+ if ((secureRandomProviders == null)
+ || (secureRandomProviders.length < 1)
+ || (!LinuxPRNGSecureRandomProvider.class.equals(
+ secureRandomProviders[0].getClass()))) {
+ Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1);
+ }
+
+ // Assert that new SecureRandom() and
+ // SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed
+ // by the Linux PRNG-based SecureRandom implementation.
+ SecureRandom rng1 = new SecureRandom();
+ if (!LinuxPRNGSecureRandomProvider.class.equals(
+ rng1.getProvider().getClass())) {
+ throw new SecurityException(
+ "new SecureRandom() backed by wrong Provider: "
+ + rng1.getProvider().getClass());
+ }
+
+ SecureRandom rng2;
+ try {
+ rng2 = SecureRandom.getInstance("SHA1PRNG");
+ } catch (NoSuchAlgorithmException e) {
+ throw new SecurityException("SHA1PRNG not available", e);
+ }
+ if (!LinuxPRNGSecureRandomProvider.class.equals(
+ rng2.getProvider().getClass())) {
+ throw new SecurityException(
+ "SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong"
+ + " Provider: " + rng2.getProvider().getClass());
+ }
+ }
+
+ /**
+ * {@code Provider} of {@code SecureRandom} engines which pass through
+ * all requests to the Linux PRNG.
+ */
+ private static class LinuxPRNGSecureRandomProvider extends Provider {
+
+ public LinuxPRNGSecureRandomProvider() {
+ super("LinuxPRNG",
+ 1.0,
+ "A Linux-specific random number provider that uses"
+ + " /dev/urandom");
+ // Although /dev/urandom is not a SHA-1 PRNG, some apps
+ // explicitly request a SHA1PRNG SecureRandom and we thus need to
+ // prevent them from getting the default implementation whose output
+ // may have low entropy.
+ put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName());
+ put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
+ }
+ }
+
+ /**
+ * {@link SecureRandomSpi} which passes all requests to the Linux PRNG
+ * ({@code /dev/urandom}).
+ */
+ public static class LinuxPRNGSecureRandom extends SecureRandomSpi {
+
+ /*
+ * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed
+ * are passed through to the Linux PRNG (/dev/urandom). Instances of
+ * this class seed themselves by mixing in the current time, PID, UID,
+ * build fingerprint, and hardware serial number (where available) into
+ * Linux PRNG.
+ *
+ * Concurrency: Read requests to the underlying Linux PRNG are
+ * serialized (on sLock) to ensure that multiple threads do not get
+ * duplicated PRNG output.
+ */
+
+ private static final File URANDOM_FILE = new File("/dev/urandom");
+
+ private static final Object sLock = new Object();
+
+ /**
+ * Input stream for reading from Linux PRNG or {@code null} if not yet
+ * opened.
+ *
+ * @GuardedBy("sLock")
+ */
+ private static DataInputStream sUrandomIn;
+
+ /**
+ * Output stream for writing to Linux PRNG or {@code null} if not yet
+ * opened.
+ *
+ * @GuardedBy("sLock")
+ */
+ private static OutputStream sUrandomOut;
+
+ /**
+ * Whether this engine instance has been seeded. This is needed because
+ * each instance needs to seed itself if the client does not explicitly
+ * seed it.
+ */
+ private boolean mSeeded;
+
+ @Override
+ protected void engineSetSeed(byte[] bytes) {
+ try {
+ OutputStream out;
+ synchronized (sLock) {
+ out = getUrandomOutputStream();
+ }
+ out.write(bytes);
+ out.flush();
+ } catch (IOException e) {
+ // On a small fraction of devices /dev/urandom is not writable.
+ // Log and ignore.
+ Log.w(PRNGFixes.class.getSimpleName(),
+ "Failed to mix seed into " + URANDOM_FILE);
+ } finally {
+ mSeeded = true;
+ }
+ }
+
+ @Override
+ protected void engineNextBytes(byte[] bytes) {
+ if (!mSeeded) {
+ // Mix in the device- and invocation-specific seed.
+ engineSetSeed(generateSeed());
+ }
+
+ try {
+ DataInputStream in;
+ synchronized (sLock) {
+ in = getUrandomInputStream();
+ }
+ synchronized (in) {
+ in.readFully(bytes);
+ }
+ } catch (IOException e) {
+ throw new SecurityException(
+ "Failed to read from " + URANDOM_FILE, e);
+ }
+ }
+
+ @Override
+ protected byte[] engineGenerateSeed(int size) {
+ byte[] seed = new byte[size];
+ engineNextBytes(seed);
+ return seed;
+ }
+
+ private DataInputStream getUrandomInputStream() {
+ synchronized (sLock) {
+ if (sUrandomIn == null) {
+ // NOTE: Consider inserting a BufferedInputStream between
+ // DataInputStream and FileInputStream if you need higher
+ // PRNG output performance and can live with future PRNG
+ // output being pulled into this process prematurely.
+ try {
+ sUrandomIn = new DataInputStream(
+ new FileInputStream(URANDOM_FILE));
+ } catch (IOException e) {
+ throw new SecurityException("Failed to open "
+ + URANDOM_FILE + " for reading", e);
+ }
+ }
+ return sUrandomIn;
+ }
+ }
+
+ private OutputStream getUrandomOutputStream() throws IOException {
+ synchronized (sLock) {
+ if (sUrandomOut == null) {
+ sUrandomOut = new FileOutputStream(URANDOM_FILE);
+ }
+ return sUrandomOut;
+ }
+ }
+ }
+
+ /**
+ * Generates a device- and invocation-specific seed to be mixed into the
+ * Linux PRNG.
+ */
+ private static byte[] generateSeed() {
+ try {
+ ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream();
+ DataOutputStream seedBufferOut =
+ new DataOutputStream(seedBuffer);
+ seedBufferOut.writeLong(System.currentTimeMillis());
+ seedBufferOut.writeLong(System.nanoTime());
+ seedBufferOut.writeInt(Process.myPid());
+ seedBufferOut.writeInt(Process.myUid());
+ seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL);
+ seedBufferOut.close();
+ return seedBuffer.toByteArray();
+ } catch (IOException e) {
+ throw new SecurityException("Failed to generate seed", e);
+ }
+ }
+
+ /**
+ * Gets the hardware serial number of this device.
+ *
+ * @return serial number or {@code null} if not available.
+ */
+ private static String getDeviceSerialNumber() {
+ // We're using the Reflection API because Build.SERIAL is only available
+ // since API Level 9 (Gingerbread, Android 2.3).
+ try {
+ return (String) Build.class.getField("SERIAL").get(null);
+ } catch (Exception ignored) {
+ return null;
+ }
+ }
+
+ private static byte[] getBuildFingerprintAndDeviceSerial() {
+ StringBuilder result = new StringBuilder();
+ String fingerprint = Build.FINGERPRINT;
+ if (fingerprint != null) {
+ result.append(fingerprint);
+ }
+ String serial = getDeviceSerialNumber();
+ if (serial != null) {
+ result.append(serial);
+ }
+ try {
+ return result.toString().getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("UTF-8 encoding not supported");
+ }
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/se/leap/openvpn/ProfileManager.java b/app/src/main/java/de/blinkt/openvpn/core/ProfileManager.java
index 07a4087a..4cfbcc8e 100644
--- a/app/src/main/java/se/leap/openvpn/ProfileManager.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/ProfileManager.java
@@ -1,4 +1,4 @@
-package se.leap.openvpn;
+package de.blinkt.openvpn.core;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -9,6 +9,9 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
+
+import de.blinkt.openvpn.VpnProfile;
+
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
@@ -33,7 +36,7 @@ public class ProfileManager {
private static VpnProfile tmpprofile=null;
- public static VpnProfile get(String key) {
+ private static VpnProfile get(String key) {
if (tmpprofile!=null && tmpprofile.getUUIDString().equals(key))
return tmpprofile;
@@ -71,8 +74,7 @@ public class ProfileManager {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c);
Editor prefsedit = prefs.edit();
- //prefsedit.putString(ONBOOTPROFILE, connectedrofile.getUUIDString());
- prefsedit.putString(ONBOOTPROFILE, VpnProfile.EXTRA_PROFILEUUID);
+ prefsedit.putString(ONBOOTPROFILE, connectedrofile.getUUIDString());
prefsedit.apply();
mLastConnectedVpn=connectedrofile;
@@ -143,13 +145,11 @@ public class ProfileManager {
vpnfile.close();
} catch (FileNotFoundException e) {
- e.printStackTrace();
+ VpnStatus.logException("saving VPN profile", e);
throw new RuntimeException(e);
} catch (IOException e) {
-
- e.printStackTrace();
+ VpnStatus.logException("saving VPN profile", e);
throw new RuntimeException(e);
-
}
}
@@ -171,7 +171,8 @@ public class ProfileManager {
// Sanity check
if(vp==null || vp.mName==null || vp.getUUID()==null)
continue;
-
+
+ vp.upgradeProfile();
profiles.put(vp.getUUID().toString(), vp);
} catch (StreamCorruptedException e) {
@@ -184,7 +185,7 @@ public class ProfileManager {
exp=e;
}
if(exp!=null) {
- exp.printStackTrace();
+ VpnStatus.logException("Loading VPN List",exp);
}
}
}
diff --git a/app/src/main/java/se/leap/openvpn/ProxyDetection.java b/app/src/main/java/de/blinkt/openvpn/core/ProxyDetection.java
index c7b3d196..47d88279 100644
--- a/app/src/main/java/se/leap/openvpn/ProxyDetection.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/ProxyDetection.java
@@ -1,4 +1,4 @@
-package se.leap.openvpn;
+package de.blinkt.openvpn.core;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
@@ -10,6 +10,7 @@ import java.net.URL;
import java.util.List;
import se.leap.bitmaskclient.R;
+import de.blinkt.openvpn.VpnProfile;
public class ProxyDetection {
static SocketAddress detectProxy(VpnProfile vp) {
@@ -26,9 +27,9 @@ public class ProxyDetection {
}
} catch (MalformedURLException e) {
- OpenVPN.logError(R.string.getproxy_error,e.getLocalizedMessage());
+ VpnStatus.logError(R.string.getproxy_error, e.getLocalizedMessage());
} catch (URISyntaxException e) {
- OpenVPN.logError(R.string.getproxy_error,e.getLocalizedMessage());
+ VpnStatus.logError(R.string.getproxy_error, e.getLocalizedMessage());
}
return null;
}
diff --git a/app/src/main/java/se/leap/openvpn/VPNLaunchHelper.java b/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java
index 418cf7e9..55fcb0ba 100644
--- a/app/src/main/java/se/leap/openvpn/VPNLaunchHelper.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java
@@ -1,15 +1,15 @@
-package se.leap.openvpn;
+package de.blinkt.openvpn.core;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import se.leap.bitmaskclient.R;
-
import android.content.Context;
import android.content.Intent;
import android.os.Build;
+import se.leap.bitmaskclient.R;
+import de.blinkt.openvpn.VpnProfile;
public class VPNLaunchHelper {
static private boolean writeMiniVPN(Context context) {
@@ -26,7 +26,7 @@ public class VPNLaunchHelper {
mvpn = context.getAssets().open("minivpn." + Build.CPU_ABI);
}
catch (IOException errabi) {
- OpenVPN.logInfo("Failed getting assets for archicture " + Build.CPU_ABI);
+ VpnStatus.logInfo("Failed getting assets for archicture " + Build.CPU_ABI);
e2=errabi;
mvpn = context.getAssets().open("minivpn." + Build.CPU_ABI2);
@@ -45,7 +45,7 @@ public class VPNLaunchHelper {
fout.close();
if(!mvpnout.setExecutable(true)) {
- OpenVPN.logMessage(0, "","Failed to set minivpn executable");
+ VpnStatus.logError("Failed to set minivpn executable");
return false;
}
@@ -53,9 +53,9 @@ public class VPNLaunchHelper {
return true;
} catch (IOException e) {
if(e2!=null)
- OpenVPN.logMessage(0, "",e2.getLocalizedMessage());
- OpenVPN.logMessage(0, "",e.getLocalizedMessage());
- e.printStackTrace();
+ VpnStatus.logException(e2);
+ VpnStatus.logException(e);
+
return false;
}
}
@@ -63,10 +63,11 @@ public class VPNLaunchHelper {
public static void startOpenVpn(VpnProfile startprofile, Context context) {
if(!writeMiniVPN(context)) {
- OpenVPN.logMessage(0, "", "Error writing minivpn binary");
+ VpnStatus.logError("Error writing minivpn binary");
return;
}
- OpenVPN.logMessage(0, "", context.getString(R.string.building_configration));
+
+ VpnStatus.logInfo(R.string.building_configration);
Intent startVPN = startprofile.prepareIntent(context);
if(startVPN!=null)
diff --git a/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java b/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java
new file mode 100644
index 00000000..c19daeb0
--- /dev/null
+++ b/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java
@@ -0,0 +1,540 @@
+package de.blinkt.openvpn.core;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.Signature;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import se.leap.bitmaskclient.R;
+
+import java.io.ByteArrayInputStream;
+import java.io.FileNotFoundException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.FormatFlagsConversionMismatchException;
+import java.util.LinkedList;
+import java.util.Locale;
+import java.util.UnknownFormatConversionException;
+import java.util.Vector;
+
+public class VpnStatus {
+
+
+ public static LinkedList<LogItem> logbuffer;
+
+ private static Vector<LogListener> logListener;
+ private static Vector<StateListener> stateListener;
+ private static Vector<ByteCountListener> byteCountListener;
+
+ private static String mLaststatemsg="";
+
+ private static String mLaststate = "NOPROCESS";
+
+ private static int mLastStateresid=R.string.state_noprocess;
+
+ private static long mlastByteCount[]={0,0,0,0};
+
+ public static void logException(LogLevel ll, String context, Exception e) {
+ StringWriter sw = new StringWriter();
+ e.printStackTrace(new PrintWriter(sw));
+ LogItem li;
+ if (context !=null) {
+ li = new LogItem(ll, R.string.unhandled_exception_context, e.getMessage(), sw.toString(), context);
+ } else {
+ li = new LogItem(ll, R.string.unhandled_exception, e.getMessage(), sw.toString());
+ }
+ newLogItem(li);
+ }
+
+ public static void logException(Exception e) {
+ logException(LogLevel.ERROR, null, e);
+ }
+
+ public static void logException(String context, Exception e) {
+ logException(LogLevel.ERROR, context, e);
+ }
+
+ private static final int MAXLOGENTRIES = 1000;
+
+ public static final String MANAGMENT_PREFIX = "M:";
+
+ public enum ConnectionStatus {
+ LEVEL_CONNECTED,
+ LEVEL_VPNPAUSED,
+ LEVEL_CONNECTING_SERVER_REPLIED,
+ LEVEL_CONNECTING_NO_SERVER_REPLY_YET,
+ LEVEL_NONETWORK,
+ LEVEL_NOTCONNECTED,
+ LEVEL_AUTH_FAILED,
+ LEVEL_WAITING_FOR_USER_INPUT,
+ UNKNOWN_LEVEL
+ }
+
+ public enum LogLevel {
+ INFO(2),
+ ERROR(-2),
+ WARNING(1),
+ VERBOSE(3),
+ DEBUG(4);
+
+ protected int mValue;
+ LogLevel(int value) {
+ mValue = value;
+ }
+
+ public int getInt() {
+ return mValue;
+ }
+
+ public static LogLevel getEnumByValue(int value) {
+ switch (value) {
+ case 1: return INFO;
+ case 2: return ERROR;
+ case 3: return WARNING;
+ case 4: return DEBUG;
+ default: return null;
+ }
+ }
+ }
+
+ // keytool -printcert -jarfile de.blinkt.openvpn_85.apk
+ public static final byte[] officalkey = {-58, -42, -44, -106, 90, -88, -87, -88, -52, -124, 84, 117, 66, 79, -112, -111, -46, 86, -37, 109};
+ public static final byte[] officaldebugkey = {-99, -69, 45, 71, 114, -116, 82, 66, -99, -122, 50, -70, -56, -111, 98, -35, -65, 105, 82, 43};
+ public static final byte[] amazonkey = {-116, -115, -118, -89, -116, -112, 120, 55, 79, -8, -119, -23, 106, -114, -85, -56, -4, 105, 26, -57};
+ public static final byte[] fdroidkey = {-92, 111, -42, -46, 123, -96, -60, 79, -27, -31, 49, 103, 11, -54, -68, -27, 17, 2, 121, 104};
+
+
+ private static ConnectionStatus mLastLevel=ConnectionStatus.LEVEL_NOTCONNECTED;
+
+ static {
+ logbuffer = new LinkedList<LogItem>();
+ logListener = new Vector<VpnStatus.LogListener>();
+ stateListener = new Vector<VpnStatus.StateListener>();
+ byteCountListener = new Vector<VpnStatus.ByteCountListener>();
+ 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)
+ for(Object o:mArgs)
+ str += "|" + o.toString();
+
+ return str;
+ }
+ }
+ } catch (UnknownFormatConversionException e) {
+ if (c != null)
+ throw new UnknownFormatConversionException(e.getLocalizedMessage() + getString(null));
+ else
+ throw e;
+ } catch (java.util.FormatFlagsConversionMismatchException e) {
+ if (c != null)
+ throw new FormatFlagsConversionMismatchException(e.getLocalizedMessage() + getString(null),e.getConversion());
+ else
+ throw e;
+ }
+
+ }
+
+ public LogLevel getLogLevel()
+ {
+ return mLevel;
+ }
+
+ // The lint is wrong here
+ @SuppressLint("StringFormatMatches")
+ private String getMobileInfoString(Context c) {
+ c.getPackageManager();
+ String apksign="error getting package signature";
+
+ String version="error getting version";
+ try {
+ Signature raw = c.getPackageManager().getPackageInfo(c.getPackageName(), PackageManager.GET_SIGNATURES).signatures[0];
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(raw.toByteArray()));
+ MessageDigest md = MessageDigest.getInstance("SHA-1");
+ byte[] der = cert.getEncoded();
+ md.update(der);
+ byte[] digest = md.digest();
+
+ if (Arrays.equals(digest, officalkey))
+ apksign = c.getString(R.string.official_build);
+ else if (Arrays.equals(digest, officaldebugkey))
+ apksign = c.getString(R.string.debug_build);
+ else if (Arrays.equals(digest, amazonkey))
+ apksign = "amazon version";
+ else if (Arrays.equals(digest, fdroidkey))
+ apksign = "F-Droid built and signed version";
+ else
+ apksign = c.getString(R.string.built_by,cert.getSubjectX500Principal().getName());
+
+ PackageInfo packageinfo = c.getPackageManager().getPackageInfo(c.getPackageName(), 0);
+ version = packageinfo.versionName;
+
+ } catch (NameNotFoundException e) {
+ } catch (CertificateException e) {
+ } catch (NoSuchAlgorithmException e) {
+ }
+
+ Object[] argsext = Arrays.copyOf(mArgs, mArgs.length+2);
+ argsext[argsext.length-1]=apksign;
+ argsext[argsext.length-2]=version;
+
+ return c.getString(R.string.mobile_info_extended, argsext);
+
+ }
+
+ public long getLogtime() {
+ return logtime;
+ }
+
+
+ public int getVerbosityLevel() {
+ if (mVerbosityLevel==-1) {
+ // Hack:
+ // For message not from OpenVPN, report the status level as log level
+ return mLevel.getInt();
+ }
+ return mVerbosityLevel;
+ }
+ }
+
+
+
+ public interface LogListener {
+ void newLog(LogItem logItem);
+ }
+
+ public interface StateListener {
+ void updateState(String state, String logmessage, int localizedResId, ConnectionStatus level);
+ }
+
+ public interface ByteCountListener {
+ void updateByteCount(long in, long out, long diffIn, long diffOut);
+ }
+
+ public synchronized static void logMessage(LogLevel level,String prefix, String message)
+ {
+ newLogItem(new LogItem(level, prefix + message));
+
+ }
+
+ public synchronized static void clearLog() {
+ logbuffer.clear();
+ logInformation();
+ }
+
+ private static void logInformation() {
+ logInfo(R.string.mobile_info,Build.MODEL, Build.BOARD,Build.BRAND,Build.VERSION.SDK_INT);
+ }
+
+ public synchronized static void addLogListener(LogListener ll){
+ logListener.add(ll);
+ }
+
+ public synchronized static void removeLogListener(LogListener ll) {
+ logListener.remove(ll);
+ }
+
+ public synchronized static void addByteCountListener(ByteCountListener bcl) {
+ bcl.updateByteCount(mlastByteCount[0], mlastByteCount[1], mlastByteCount[2], mlastByteCount[3]);
+ byteCountListener.add(bcl);
+ }
+
+ public synchronized static void removeByteCountListener(ByteCountListener bcl) {
+ byteCountListener.remove(bcl);
+ }
+
+
+ public synchronized static void addStateListener(StateListener sl){
+ if(!stateListener.contains(sl)){
+ stateListener.add(sl);
+ if(mLaststate!=null)
+ sl.updateState(mLaststate, mLaststatemsg, mLastStateresid, mLastLevel);
+ }
+ }
+
+ private static int getLocalizedState(String state){
+ if (state.equals("CONNECTING"))
+ return R.string.state_connecting;
+ else if (state.equals("WAIT"))
+ return R.string.state_wait;
+ else if (state.equals("AUTH"))
+ return R.string.state_auth;
+ else if (state.equals("GET_CONFIG"))
+ return R.string.state_get_config;
+ else if (state.equals("ASSIGN_IP"))
+ return R.string.state_assign_ip;
+ else if (state.equals("ADD_ROUTES"))
+ return R.string.state_add_routes;
+ else if (state.equals("CONNECTED"))
+ return R.string.state_connected;
+ else if (state.equals("DISCONNECTED"))
+ return R.string.state_disconnected;
+ else if (state.equals("RECONNECTING"))
+ return R.string.state_reconnecting;
+ else if (state.equals("EXITING"))
+ return R.string.state_exiting;
+ else if (state.equals("RESOLVE"))
+ return R.string.state_resolve;
+ else if (state.equals("TCP_CONNECT"))
+ return R.string.state_tcp_connect;
+ else
+ return R.string.unknown_state;
+
+ }
+
+ public static void updateStatePause(OpenVPNManagement.pauseReason pauseReason) {
+ switch (pauseReason) {
+ case noNetwork:
+ VpnStatus.updateStateString("NONETWORK", "", R.string.state_nonetwork, ConnectionStatus.LEVEL_NONETWORK);
+ break;
+ case screenOff:
+ VpnStatus.updateStateString("SCREENOFF", "", R.string.state_screenoff, ConnectionStatus.LEVEL_VPNPAUSED);
+ break;
+ case userPause:
+ VpnStatus.updateStateString("USERPAUSE", "", R.string.state_userpause, ConnectionStatus.LEVEL_VPNPAUSED);
+ break;
+ }
+
+ }
+
+ private static ConnectionStatus getLevel(String state){
+ String[] noreplyet = {"CONNECTING","WAIT", "RECONNECTING", "RESOLVE", "TCP_CONNECT"};
+ String[] reply = {"AUTH","GET_CONFIG","ASSIGN_IP","ADD_ROUTES"};
+ String[] connected = {"CONNECTED"};
+ String[] notconnected = {"DISCONNECTED", "EXITING"};
+
+ for(String x:noreplyet)
+ if(state.equals(x))
+ return ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET;
+
+ for(String x:reply)
+ if(state.equals(x))
+ return ConnectionStatus.LEVEL_CONNECTING_SERVER_REPLIED;
+
+ for(String x:connected)
+ if(state.equals(x))
+ return ConnectionStatus.LEVEL_CONNECTED;
+
+ for(String x:notconnected)
+ if(state.equals(x))
+ return ConnectionStatus.LEVEL_NOTCONNECTED;
+
+ return ConnectionStatus.UNKNOWN_LEVEL;
+
+ }
+
+
+
+
+ public synchronized static void removeStateListener(StateListener sl) {
+ stateListener.remove(sl);
+ }
+
+
+ synchronized public static LogItem[] getlogbuffer() {
+
+ // The stoned way of java to return an array from a vector
+ // brought to you by eclipse auto complete
+ return logbuffer.toArray(new LogItem[logbuffer.size()]);
+
+ }
+
+ public static void updateStateString (String state, String msg) {
+ int rid = getLocalizedState(state);
+ ConnectionStatus level = getLevel(state);
+ updateStateString(state, msg, rid, level);
+ }
+
+ public synchronized static void updateStateString(String state, String msg, int resid, ConnectionStatus level) {
+ // Workound for OpenVPN doing AUTH and wait and being connected
+ // Simply ignore these state
+ if (mLastLevel == ConnectionStatus.LEVEL_CONNECTED &&
+ (state.equals("WAIT") || state.equals("AUTH")))
+ {
+ newLogItem(new LogItem((LogLevel.DEBUG), String.format("Ignoring OpenVPN Status in CONNECTED state (%s->%s): %s",state,level.toString(),msg)));
+ return;
+ }
+
+ mLaststate= state;
+ mLaststatemsg = msg;
+ mLastStateresid = resid;
+ mLastLevel = level;
+
+
+
+ for (StateListener sl : stateListener) {
+ sl.updateState(state,msg,resid,level);
+ }
+ //newLogItem(new LogItem((LogLevel.DEBUG), String.format("New OpenVPN Status (%s->%s): %s",state,level.toString(),msg)));
+ }
+
+ public static void logInfo(String message) {
+ newLogItem(new LogItem(LogLevel.INFO, message));
+ }
+
+ public static void logInfo(int resourceId, Object... args) {
+ newLogItem(new LogItem(LogLevel.INFO, resourceId, args));
+ }
+
+ public static void logDebug(int resourceId, Object... args) {
+ newLogItem(new LogItem(LogLevel.DEBUG, resourceId, args));
+ }
+
+
+ private synchronized static void newLogItem(LogItem logItem) {
+ logbuffer.addLast(logItem);
+ if(logbuffer.size()>MAXLOGENTRIES)
+ logbuffer.removeFirst();
+
+ for (LogListener ll : logListener) {
+ ll.newLog(logItem);
+ }
+ }
+
+ public static void logError(String msg) {
+ newLogItem(new LogItem(LogLevel.ERROR, msg));
+
+ }
+
+ public static void logWarning(int resourceId, Object... args) {
+ newLogItem(new LogItem(LogLevel.WARNING, resourceId, args));
+ }
+
+ public static void logWarning(String msg) {
+ newLogItem(new LogItem(LogLevel.WARNING, msg));
+ }
+
+
+ public static void logError(int resourceId) {
+ newLogItem(new LogItem(LogLevel.ERROR, resourceId));
+ }
+ public static void logError(int resourceId, Object... args) {
+ newLogItem(new LogItem(LogLevel.ERROR, resourceId, args));
+ }
+
+ public static void logMessageOpenVPN(LogLevel level, int ovpnlevel, String message) {
+ newLogItem(new LogItem(level, ovpnlevel, message));
+
+ }
+
+
+ public static synchronized void updateByteCount(long in, long out) {
+ long lastIn = mlastByteCount[0];
+ long lastOut = mlastByteCount[1];
+ long diffIn = mlastByteCount[2] = in - lastIn;
+ long diffOut = mlastByteCount[3] = out - lastOut;
+
+
+
+ mlastByteCount = new long[] {in,out,diffIn,diffOut};
+ for(ByteCountListener bcl:byteCountListener){
+ bcl.updateByteCount(in, out, diffIn,diffOut);
+ }
+ }
+
+
+
+}
diff --git a/app/src/main/java/de/blinkt/openvpn/core/X509Utils.java b/app/src/main/java/de/blinkt/openvpn/core/X509Utils.java
new file mode 100644
index 00000000..35e53c08
--- /dev/null
+++ b/app/src/main/java/de/blinkt/openvpn/core/X509Utils.java
@@ -0,0 +1,155 @@
+package de.blinkt.openvpn.core;
+
+import android.content.Context;
+import android.text.TextUtils;
+
+import se.leap.bitmaskclient.R;
+import de.blinkt.openvpn.VpnProfile;
+import org.spongycastle.util.io.pem.PemObject;
+import org.spongycastle.util.io.pem.PemReader;
+
+
+import javax.security.auth.x500.X500Principal;
+import java.io.*;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Hashtable;
+
+public class X509Utils {
+ public static Certificate getCertificateFromFile(String certfilename) throws FileNotFoundException, CertificateException {
+ CertificateFactory certFact = CertificateFactory.getInstance("X.509");
+
+ InputStream inStream;
+
+ if(VpnProfile.isEmbedded(certfilename)) {
+ // The java certifcate reader is ... kind of stupid
+ // It does NOT ignore chars before the --BEGIN ...
+ int subIndex = certfilename.indexOf("-----BEGIN CERTIFICATE-----");
+ subIndex = Math.max(0,subIndex);
+ inStream = new ByteArrayInputStream(certfilename.substring(subIndex).getBytes());
+
+
+ } else {
+ inStream = new FileInputStream(certfilename);
+ }
+
+
+ return certFact.generateCertificate(inStream);
+ }
+
+ public static PemObject readPemObjectFromFile (String keyfilename) throws IOException {
+
+ Reader inStream;
+
+ if(VpnProfile.isEmbedded(keyfilename))
+ inStream = new StringReader(VpnProfile.getEmbeddedContent(keyfilename));
+ else
+ inStream = new FileReader(new File(keyfilename));
+
+ PemReader pr = new PemReader(inStream);
+ PemObject r = pr.readPemObject();
+ pr.close();
+ return r;
+ }
+
+
+
+
+ public static String getCertificateFriendlyName (Context c, String filename) {
+ if(!TextUtils.isEmpty(filename)) {
+ try {
+ X509Certificate cert = (X509Certificate) getCertificateFromFile(filename);
+
+ return getCertificateFriendlyName(cert);
+
+ } catch (Exception e) {
+ VpnStatus.logError("Could not read certificate" + e.getLocalizedMessage());
+ }
+ }
+ return c.getString(R.string.cannotparsecert);
+ }
+
+ public static String getCertificateFriendlyName(X509Certificate cert) {
+ X500Principal principal = cert.getSubjectX500Principal();
+ byte[] encodedSubject = principal.getEncoded();
+ String friendlyName=null;
+
+ /* Hack so we do not have to ship a whole Spongy/bouncycastle */
+ Exception exp=null;
+ try {
+ Class X509NameClass = Class.forName("com.android.org.bouncycastle.asn1.x509.X509Name");
+ Method getInstance = X509NameClass.getMethod("getInstance",Object.class);
+
+ Hashtable defaultSymbols = (Hashtable) X509NameClass.getField("DefaultSymbols").get(X509NameClass);
+
+ if (!defaultSymbols.containsKey("1.2.840.113549.1.9.1"))
+ defaultSymbols.put("1.2.840.113549.1.9.1","eMail");
+
+ Object subjectName = getInstance.invoke(X509NameClass, encodedSubject);
+
+ Method toString = X509NameClass.getMethod("toString",boolean.class,Hashtable.class);
+
+ friendlyName= (String) toString.invoke(subjectName,true,defaultSymbols);
+
+ } catch (ClassNotFoundException e) {
+ exp =e ;
+ } catch (NoSuchMethodException e) {
+ exp =e;
+ } catch (InvocationTargetException e) {
+ exp =e;
+ } catch (IllegalAccessException e) {
+ exp =e;
+ } catch (NoSuchFieldException e) {
+ exp =e;
+ }
+ if (exp!=null)
+ VpnStatus.logException("Getting X509 Name from certificate", exp);
+
+ /* Fallback if the reflection method did not work */
+ if(friendlyName==null)
+ friendlyName = principal.getName();
+
+
+ // Really evil hack to decode email address
+ // See: http://code.google.com/p/android/issues/detail?id=21531
+
+ String[] parts = friendlyName.split(",");
+ for (int i=0;i<parts.length;i++){
+ String part = parts[i];
+ if (part.startsWith("1.2.840.113549.1.9.1=#16")) {
+ parts[i] = "email=" + ia5decode(part.replace("1.2.840.113549.1.9.1=#16", ""));
+ }
+ }
+ friendlyName = TextUtils.join(",", parts);
+ return friendlyName;
+ }
+
+ public static boolean isPrintableChar(char c) {
+ Character.UnicodeBlock block = Character.UnicodeBlock.of( c );
+ return (!Character.isISOControl(c)) &&
+ block != null &&
+ block != Character.UnicodeBlock.SPECIALS;
+ }
+
+ private static String ia5decode(String ia5string) {
+ String d = "";
+ for (int i=1;i<ia5string.length();i=i+2) {
+ String hexstr = ia5string.substring(i-1,i+1);
+ char c = (char) Integer.parseInt(hexstr,16);
+ if (isPrintableChar(c)) {
+ d+=c;
+ } else if (i==1 && (c==0x12 || c==0x1b)) {
+ ; // ignore
+ } else {
+ d += "\\x" + hexstr;
+ }
+ }
+ return d;
+ }
+
+
+}
diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java b/app/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java
new file mode 100644
index 00000000..2f04d235
--- /dev/null
+++ b/app/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java
@@ -0,0 +1,663 @@
+package de.blinkt.openvpn.fragments;
+
+import se.leap.bitmaskclient.R;
+
+import se.leap.bitmaskclient.R;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.app.*;
+import android.content.*;
+import android.database.DataSetObserver;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Handler.Callback;
+import android.os.IBinder;
+import android.os.Message;
+import android.text.SpannableString;
+import android.text.format.DateFormat;
+import android.text.style.ImageSpan;
+import android.view.*;
+import android.widget.*;
+import android.widget.AdapterView.OnItemLongClickListener;
+import de.blinkt.openvpn.*;
+import de.blinkt.openvpn.activities.DisconnectVPN;
+import se.leap.bitmaskclient.Dashboard;
+import de.blinkt.openvpn.core.OpenVPNManagement;
+import de.blinkt.openvpn.core.VpnStatus;
+import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus;
+import de.blinkt.openvpn.core.VpnStatus.LogItem;
+import de.blinkt.openvpn.core.VpnStatus.LogListener;
+import de.blinkt.openvpn.core.VpnStatus.StateListener;
+import de.blinkt.openvpn.core.OpenVpnService;
+import de.blinkt.openvpn.core.OpenVpnService.LocalBinder;
+import de.blinkt.openvpn.core.ProfileManager;
+import org.jetbrains.annotations.Nullable;
+
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Vector;
+
+import static de.blinkt.openvpn.core.OpenVpnService.humanReadableByteCount;
+
+public class LogFragment extends ListFragment implements StateListener, SeekBar.OnSeekBarChangeListener, RadioGroup.OnCheckedChangeListener, VpnStatus.ByteCountListener {
+ private static final String LOGTIMEFORMAT = "logtimeformat";
+ private static final int START_VPN_CONFIG = 0;
+ private static final String VERBOSITYLEVEL = "verbositylevel";
+ protected OpenVpnService mService;
+ private ServiceConnection mConnection = new ServiceConnection() {
+
+
+ @Override
+ public void onServiceConnected(ComponentName className,
+ IBinder service) {
+ // We've bound to LocalService, cast the IBinder and get LocalService instance
+ LocalBinder binder = (LocalBinder) service;
+ mService = binder.getService();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName arg0) {
+ mService =null;
+ }
+
+ };
+
+ private SeekBar mLogLevelSlider;
+ private LinearLayout mOptionsLayout;
+ private RadioGroup mTimeRadioGroup;
+ private TextView mUpStatus;
+ private TextView mDownStatus;
+ private TextView mConnectStatus;
+ private boolean mShowOptionsLayout;
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ ladapter.setLogLevel(progress+1);
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ }
+
+ @Override
+ public void onCheckedChanged(RadioGroup group, int checkedId) {
+ switch (checkedId) {
+ case R.id.radioISO:
+ ladapter.setTimeFormat(LogWindowListAdapter.TIME_FORMAT_ISO);
+ break;
+ case R.id.radioNone:
+ ladapter.setTimeFormat(LogWindowListAdapter.TIME_FORMAT_NONE);
+ break;
+ case R.id.radioShort:
+ ladapter.setTimeFormat(LogWindowListAdapter.TIME_FORMAT_SHORT);
+ break;
+
+ }
+ }
+
+ @Override
+ public void updateByteCount(long in, long out, long diffIn, long diffOut) {
+ //%2$s/s %1$s - ↑%4$s/s %3$s
+ final String down = String.format("%2$s/s %1$s", humanReadableByteCount(in, false), humanReadableByteCount(diffIn / OpenVPNManagement.mBytecountInterval, true));
+ final String up = String.format("%2$s/s %1$s", humanReadableByteCount(out, false), humanReadableByteCount(diffOut / OpenVPNManagement.mBytecountInterval, true));
+
+ if (mUpStatus != null && mDownStatus != null) {
+ if (getActivity() != null) {
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mUpStatus.setText(up);
+ mDownStatus.setText(down);
+ }
+ });
+ }
+ }
+
+ }
+
+
+ class LogWindowListAdapter implements ListAdapter, LogListener, Callback {
+
+ private static final int MESSAGE_NEWLOG = 0;
+
+ private static final int MESSAGE_CLEARLOG = 1;
+
+ private static final int MESSAGE_NEWTS = 2;
+ private static final int MESSAGE_NEWLOGLEVEL = 3;
+
+ public static final int TIME_FORMAT_NONE = 0;
+ public static final int TIME_FORMAT_SHORT = 1;
+ public static final int TIME_FORMAT_ISO = 2;
+ private static final int MAX_STORED_LOG_ENTRIES = 1000;
+
+ private Vector<LogItem> allEntries=new Vector<LogItem>();
+
+ private Vector<LogItem> currentLevelEntries=new Vector<LogItem>();
+
+ private Handler mHandler;
+
+ private Vector<DataSetObserver> observers=new Vector<DataSetObserver>();
+
+ private int mTimeFormat=0;
+ private int mLogLevel=3;
+
+
+ public LogWindowListAdapter() {
+ initLogBuffer();
+ if (mHandler == null) {
+ mHandler = new Handler(this);
+ }
+
+ VpnStatus.addLogListener(this);
+ }
+
+
+
+ private void initLogBuffer() {
+ allEntries.clear();
+ Collections.addAll(allEntries, VpnStatus.getlogbuffer());
+ initCurrentMessages();
+ }
+
+ String getLogStr() {
+ String str = "";
+ for(LogItem entry:allEntries) {
+ str+=getTime(entry, TIME_FORMAT_ISO) + entry.getString(getActivity()) + '\n';
+ }
+ return str;
+ }
+
+
+ private void shareLog() {
+ Intent shareIntent = new Intent(Intent.ACTION_SEND);
+ shareIntent.putExtra(Intent.EXTRA_TEXT, getLogStr());
+ shareIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.ics_openvpn_log_file));
+ shareIntent.setType("text/plain");
+ startActivity(Intent.createChooser(shareIntent, "Send Logfile"));
+ }
+
+ @Override
+ public void registerDataSetObserver(DataSetObserver observer) {
+ observers.add(observer);
+
+ }
+
+ @Override
+ public void unregisterDataSetObserver(DataSetObserver observer) {
+ observers.remove(observer);
+ }
+
+ @Override
+ public int getCount() {
+ return currentLevelEntries.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return currentLevelEntries.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return ((Object)currentLevelEntries.get(position)).hashCode();
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ TextView v;
+ if(convertView==null)
+ v = new TextView(getActivity());
+ else
+ v = (TextView) convertView;
+
+ LogItem le = currentLevelEntries.get(position);
+ String msg = le.getString(getActivity());
+ String time = getTime(le, mTimeFormat);
+ msg = time + msg;
+
+ int spanStart = time.length();
+
+ SpannableString t = new SpannableString(msg);
+
+ //t.setSpan(getSpanImage(le,(int)v.getTextSize()),spanStart,spanStart+1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ v.setText(t);
+ return v;
+ }
+
+ private String getTime(LogItem le, int time) {
+ if (time != TIME_FORMAT_NONE) {
+ Date d = new Date(le.getLogtime());
+ java.text.DateFormat timeformat;
+ if (time== TIME_FORMAT_ISO)
+ timeformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
+ else
+ timeformat = DateFormat.getTimeFormat(getActivity());
+
+ return timeformat.format(d) + " ";
+
+ } else {
+ return "";
+ }
+
+ }
+
+ private ImageSpan getSpanImage(LogItem li, int imageSize) {
+ int imageRes = android.R.drawable.ic_menu_call;
+
+ switch (li.getLogLevel()) {
+ case ERROR:
+ imageRes = android.R.drawable.ic_notification_clear_all;
+ break;
+ case INFO:
+ imageRes = android.R.drawable.ic_menu_compass;
+ break;
+ case VERBOSE:
+ imageRes = android.R.drawable.ic_menu_info_details;
+ break;
+ case WARNING:
+ imageRes = android.R.drawable.ic_menu_camera;
+ break;
+ }
+
+ Drawable d = getResources().getDrawable(imageRes);
+
+
+ //d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
+ d.setBounds(0, 0, imageSize, imageSize);
+ ImageSpan span = new ImageSpan(d, ImageSpan.ALIGN_BOTTOM);
+
+ return span;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return 0;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 1;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return currentLevelEntries.isEmpty();
+
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return true;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return true;
+ }
+
+ @Override
+ public void newLog(LogItem logMessage) {
+ Message msg = Message.obtain();
+ assert (msg!=null);
+ msg.what=MESSAGE_NEWLOG;
+ Bundle bundle=new Bundle();
+ bundle.putParcelable("logmessage", logMessage);
+ msg.setData(bundle);
+ mHandler.sendMessage(msg);
+ }
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ // We have been called
+ if(msg.what==MESSAGE_NEWLOG) {
+
+ LogItem logMessage = msg.getData().getParcelable("logmessage");
+ if(addLogMessage(logMessage))
+ for (DataSetObserver observer : observers) {
+ observer.onChanged();
+ }
+ } else if (msg.what == MESSAGE_CLEARLOG) {
+ for (DataSetObserver observer : observers) {
+ observer.onInvalidated();
+ }
+ initLogBuffer();
+ } else if (msg.what == MESSAGE_NEWTS) {
+ for (DataSetObserver observer : observers) {
+ observer.onInvalidated();
+ }
+ } else if (msg.what == MESSAGE_NEWLOGLEVEL) {
+ initCurrentMessages();
+
+ for (DataSetObserver observer: observers) {
+ observer.onChanged();
+ }
+
+ }
+
+ return true;
+ }
+
+ private void initCurrentMessages() {
+ currentLevelEntries.clear();
+ for(LogItem li: allEntries) {
+ if (li.getVerbosityLevel() <= mLogLevel ||
+ mLogLevel == VpnProfile.MAXLOGLEVEL)
+ currentLevelEntries.add(li);
+ }
+ }
+
+ /**
+ *
+ * @param logmessage
+ * @return True if the current entries have changed
+ */
+ private boolean addLogMessage(LogItem logmessage) {
+ allEntries.add(logmessage);
+
+ if (allEntries.size() > MAX_STORED_LOG_ENTRIES) {
+ Vector<LogItem> oldAllEntries = allEntries;
+ allEntries = new Vector<LogItem>(allEntries.size());
+ for (int i=50;i<oldAllEntries.size();i++) {
+ allEntries.add(oldAllEntries.elementAt(i));
+ }
+ initCurrentMessages();
+ return true;
+ } else {
+ if (logmessage.getVerbosityLevel() <= mLogLevel) {
+ currentLevelEntries.add(logmessage);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ void clearLog() {
+ // Actually is probably called from GUI Thread as result of the user
+ // pressing a button. But better safe than sorry
+ VpnStatus.clearLog();
+ VpnStatus.logInfo(R.string.logCleared);
+ mHandler.sendEmptyMessage(MESSAGE_CLEARLOG);
+ }
+
+
+
+ public void setTimeFormat(int newTimeFormat) {
+ mTimeFormat= newTimeFormat;
+ mHandler.sendEmptyMessage(MESSAGE_NEWTS);
+ }
+
+ public void setLogLevel(int logLevel) {
+ mLogLevel = logLevel;
+ mHandler.sendEmptyMessage(MESSAGE_NEWLOGLEVEL);
+ }
+
+ }
+
+
+
+ private LogWindowListAdapter ladapter;
+ private TextView mSpeedView;
+
+
+
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if(item.getItemId()==R.id.clearlog) {
+ ladapter.clearLog();
+ return true;
+ } else if(item.getItemId()==R.id.cancel){
+ Intent intent = new Intent(getActivity(),DisconnectVPN.class);
+ startActivity(intent);
+ return true;
+ } else if(item.getItemId()==R.id.send) {
+ ladapter.shareLog();
+ } else if(item.getItemId() == R.id.toggle_time) {
+ showHideOptionsPanel();
+ } else if(item.getItemId() == android.R.id.home) {
+ // This is called when the Home (Up) button is pressed
+ // in the Action Bar.
+ Intent parentActivityIntent = new Intent(getActivity(), Dashboard.class);
+ parentActivityIntent.addFlags(
+ Intent.FLAG_ACTIVITY_CLEAR_TOP |
+ Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(parentActivityIntent);
+ getActivity().finish();
+ return true;
+
+ }
+ return super.onOptionsItemSelected(item);
+
+ }
+
+ private void showHideOptionsPanel() {
+ boolean optionsVisible = (mOptionsLayout.getVisibility() != View.GONE);
+
+ ObjectAnimator anim;
+ if (optionsVisible) {
+ anim = ObjectAnimator.ofFloat(mOptionsLayout,"alpha",1.0f, 0f);
+ anim.addListener(collapseListener);
+
+ } else {
+ mOptionsLayout.setVisibility(View.VISIBLE);
+ anim = ObjectAnimator.ofFloat(mOptionsLayout,"alpha", 0f, 1.0f);
+ //anim = new TranslateAnimation(0.0f, 0.0f, mOptionsLayout.getHeight(), 0.0f);
+
+ }
+
+ //anim.setInterpolator(new AccelerateInterpolator(1.0f));
+ //anim.setDuration(300);
+ //mOptionsLayout.startAnimation(anim);
+ anim.start();
+
+ }
+
+ AnimatorListenerAdapter collapseListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ mOptionsLayout.setVisibility(View.GONE);
+ }
+
+ };
+
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ inflater.inflate(R.menu.logmenu, menu);
+ if (getResources().getBoolean(R.bool.logSildersAlwaysVisible))
+ menu.removeItem(R.id.toggle_time);
+ }
+
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ VpnStatus.addStateListener(this);
+ VpnStatus.addByteCountListener(this);
+ Intent intent = new Intent(getActivity(), OpenVpnService.class);
+ intent.setAction(OpenVpnService.START_SERVICE);
+
+ getActivity().bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+
+
+
+ }
+
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == START_VPN_CONFIG && resultCode== Activity.RESULT_OK) {
+ String configuredVPN = data.getStringExtra(VpnProfile.EXTRA_PROFILEUUID);
+
+ final VpnProfile profile = ProfileManager.get(getActivity(),configuredVPN);
+ ProfileManager.getInstance(getActivity()).saveProfile(getActivity(), profile);
+ // Name could be modified, reset List adapter
+
+ AlertDialog.Builder dialog = new AlertDialog.Builder(getActivity());
+ dialog.setTitle(R.string.configuration_changed);
+ dialog.setMessage(R.string.restart_vpn_after_change);
+
+
+ dialog.setPositiveButton(R.string.restart,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Intent intent = new Intent(getActivity(), LaunchVPN.class);
+ intent.putExtra(LaunchVPN.EXTRA_KEY, profile.getUUIDString());
+ intent.setAction(Intent.ACTION_MAIN);
+ startActivity(intent);
+ }
+
+
+ });
+ dialog.setNegativeButton(R.string.ignore, null);
+ dialog.create().show();
+ }
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ VpnStatus.removeStateListener(this);
+ VpnStatus.removeByteCountListener(this);
+
+ if(mService!=null)
+ getActivity().unbindService(mConnection);
+ getActivity().getPreferences(0).edit().putInt(LOGTIMEFORMAT, ladapter.mTimeFormat)
+ .putInt(VERBOSITYLEVEL, ladapter.mLogLevel).apply();
+
+ }
+
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ ListView lv = getListView();
+
+ lv.setOnItemLongClickListener(new OnItemLongClickListener() {
+
+ @Override
+ public boolean onItemLongClick(AdapterView<?> parent, View view,
+ int position, long id) {
+ ClipboardManager clipboard = (ClipboardManager)
+ getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
+ ClipData clip = ClipData.newPlainText("Log Entry",((TextView) view).getText());
+ clipboard.setPrimaryClip(clip);
+ Toast.makeText(getActivity(), R.string.copied_entry, Toast.LENGTH_SHORT).show();
+ return true;
+ }
+ });
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View v = inflater.inflate(R.layout.log_fragment, container, false);
+
+ setHasOptionsMenu(true);
+
+ ladapter = new LogWindowListAdapter();
+ ladapter.mTimeFormat = getActivity().getPreferences(0).getInt(LOGTIMEFORMAT, 0);
+ int logLevel = getActivity().getPreferences(0).getInt(VERBOSITYLEVEL, 0);
+ ladapter.setLogLevel(logLevel);
+
+ setListAdapter(ladapter);
+
+ mTimeRadioGroup = (RadioGroup) v.findViewById(R.id.timeFormatRadioGroup);
+ mTimeRadioGroup.setOnCheckedChangeListener(this);
+
+ if(ladapter.mTimeFormat== LogWindowListAdapter.TIME_FORMAT_ISO) {
+ mTimeRadioGroup.check(R.id.radioISO);
+ } else if (ladapter.mTimeFormat == LogWindowListAdapter.TIME_FORMAT_NONE) {
+ mTimeRadioGroup.check(R.id.radioNone);
+ } else if (ladapter.mTimeFormat == LogWindowListAdapter.TIME_FORMAT_SHORT) {
+ mTimeRadioGroup.check(R.id.radioShort);
+ }
+
+ mSpeedView = (TextView) v.findViewById(R.id.speed);
+
+ mOptionsLayout = (LinearLayout) v.findViewById(R.id.logOptionsLayout);
+ mLogLevelSlider = (SeekBar) v.findViewById(R.id.LogLevelSlider);
+ mLogLevelSlider.setMax(VpnProfile.MAXLOGLEVEL-1);
+ mLogLevelSlider.setProgress(logLevel-1);
+
+ mLogLevelSlider.setOnSeekBarChangeListener(this);
+
+ if(getResources().getBoolean(R.bool.logSildersAlwaysVisible))
+ mOptionsLayout.setVisibility(View.VISIBLE);
+
+ mUpStatus = (TextView) v.findViewById(R.id.speedUp);
+ mDownStatus = (TextView) v.findViewById(R.id.speedDown);
+ mConnectStatus = (TextView) v.findViewById(R.id.speedStatus);
+ if (mShowOptionsLayout)
+ mOptionsLayout.setVisibility(View.VISIBLE);
+ return v;
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ if(getResources().getBoolean(R.bool.logSildersAlwaysVisible)) {
+ mShowOptionsLayout=true;
+ if (mOptionsLayout!= null)
+ mOptionsLayout.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ //getActionBar().setDisplayHomeAsUpEnabled(true);
+
+ }
+
+
+ @Override
+ public void updateState(final String status, final String logMessage, final int resId, final ConnectionStatus level) {
+ if (isAdded()) {
+ getActivity().runOnUiThread(new Runnable() {
+
+ @Override
+ public void run() {
+ if (isAdded()) {
+ String prefix = getString(resId) + ":";
+ if (status.equals("BYTECOUNT") || status.equals("NOPROCESS"))
+ prefix = "";
+ if (resId == R.string.unknown_state)
+ prefix += status;
+ if (mSpeedView != null)
+ mSpeedView.setText(prefix + logMessage);
+
+ if (mConnectStatus != null)
+ mConnectStatus.setText(getString(resId));
+ }
+ }
+ });
+ }
+ }
+
+
+ @Override
+ public void onDestroy() {
+ VpnStatus.removeLogListener(ladapter);
+ super.onDestroy();
+ }
+
+}
diff --git a/app/src/main/java/de/blinkt/openvpn/views/SeekBarTicks.java b/app/src/main/java/de/blinkt/openvpn/views/SeekBarTicks.java
new file mode 100644
index 00000000..88e8e164
--- /dev/null
+++ b/app/src/main/java/de/blinkt/openvpn/views/SeekBarTicks.java
@@ -0,0 +1,69 @@
+package de.blinkt.openvpn.views;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.ViewConfiguration;
+import android.widget.SeekBar;
+
+public class SeekBarTicks extends SeekBar {
+ private Paint mTickPaint;
+ private float mTickHeight;
+
+ private float tickHeightRatio = 0.6f;
+
+ public SeekBarTicks(Context context, AttributeSet attrs) {
+ super (context, attrs);
+
+ initTicks (context, attrs, android.R.attr.seekBarStyle);
+ }
+
+
+ public SeekBarTicks(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ initTicks (context, attrs, defStyle);
+
+ /*mTickHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ tickHeightDP,
+ ctx.getResources().getDisplayMetrics()); */
+ }
+
+ private void initTicks(Context context, AttributeSet attrs, int defStyle) {
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ new int[] { android.R.attr.secondaryProgress }, defStyle, 0);
+
+
+ int tickColor = a.getColor(0, android.R.color.black);
+ mTickPaint = new Paint();
+ mTickPaint.setColor( context.getResources().getColor(tickColor));
+ a.recycle();
+ }
+
+
+ @Override
+ protected synchronized void onDraw(Canvas canvas) {
+ drawTicks(canvas);
+ super.onDraw(canvas);
+ }
+
+ private void drawTicks(Canvas canvas) {
+
+ final int available = getWidth() - getPaddingLeft() - getPaddingRight();
+ final int availableHeight = getHeight() - getPaddingBottom() - getPaddingTop();
+
+ int extrapadding = (int) ((availableHeight- (availableHeight * tickHeightRatio))/2);
+
+ int tickSpacing = available / (getMax() );
+
+ for (int i = 1; i < getMax(); i++) {
+ final float x = getPaddingLeft() + i * tickSpacing;
+
+ canvas.drawLine(x, getPaddingTop()+extrapadding, x, getHeight()-getPaddingBottom()-extrapadding, mTickPaint);
+ }
+ }
+}
diff --git a/app/src/main/java/org/spongycastle/util/io/pem/PemReader.java b/app/src/main/java/org/spongycastle/util/io/pem/PemReader.java
new file mode 100644
index 00000000..cbbebab9
--- /dev/null
+++ b/app/src/main/java/org/spongycastle/util/io/pem/PemReader.java
@@ -0,0 +1,84 @@
+package org.spongycastle.util.io.pem;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.spongycastle.util.encoders.Base64;
+
+public class PemReader
+ extends BufferedReader
+{
+ private static final String BEGIN = "-----BEGIN ";
+ private static final String END = "-----END ";
+
+ public PemReader(Reader reader)
+ {
+ super(reader);
+ }
+
+ public PemObject readPemObject()
+ throws IOException
+ {
+ String line = readLine();
+
+ while (line != null && !line.startsWith(BEGIN))
+ {
+ line = readLine();
+ }
+
+ if (line != null)
+ {
+ line = line.substring(BEGIN.length());
+ int index = line.indexOf('-');
+ String type = line.substring(0, index);
+
+ if (index > 0)
+ {
+ return loadObject(type);
+ }
+ }
+
+ return null;
+ }
+
+ private PemObject loadObject(String type)
+ throws IOException
+ {
+ String line;
+ String endMarker = END + type;
+ StringBuffer buf = new StringBuffer();
+ List headers = new ArrayList();
+
+ while ((line = readLine()) != null)
+ {
+ if (line.indexOf(":") >= 0)
+ {
+ int index = line.indexOf(':');
+ String hdr = line.substring(0, index);
+ String value = line.substring(index + 1).trim();
+
+ headers.add(new PemHeader(hdr, value));
+
+ continue;
+ }
+
+ if (line.indexOf(endMarker) != -1)
+ {
+ break;
+ }
+
+ buf.append(line.trim());
+ }
+
+ if (line == null)
+ {
+ throw new IOException(endMarker + " not found");
+ }
+
+ return new PemObject(type, headers, Base64.decode(buf.toString()));
+ }
+
+}
diff --git a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java
index 6f18b79a..f2763d84 100644
--- a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java
+++ b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java
@@ -22,6 +22,7 @@ import org.json.JSONObject;
import se.leap.bitmaskclient.R;
import se.leap.bitmaskclient.ProviderAPIResultReceiver.Receiver;
import se.leap.bitmaskclient.SignUpDialog;
+import de.blinkt.openvpn.activities.LogWindow;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.DialogFragment;
@@ -228,6 +229,11 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf
intent = new Intent(this, AboutActivity.class);
startActivity(intent);
return true;
+ case R.id.log_window:
+ Intent startLW = new Intent(getAppContext(), LogWindow.class);
+ startLW.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+ startActivity(startLW);
+ return true;
case R.id.switch_provider:
if (Provider.getInstance().hasEIP()){
if (getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).getBoolean(EIP.AUTHED_EIP, false)){
diff --git a/app/src/main/java/se/leap/bitmaskclient/EIP.java b/app/src/main/java/se/leap/bitmaskclient/EIP.java
index 68688b90..59faf93f 100644
--- a/app/src/main/java/se/leap/bitmaskclient/EIP.java
+++ b/app/src/main/java/se/leap/bitmaskclient/EIP.java
@@ -31,14 +31,15 @@ import org.json.JSONException;
import org.json.JSONObject;
import se.leap.bitmaskclient.R;
-import se.leap.openvpn.ConfigParser;
-import se.leap.openvpn.ConfigParser.ConfigParseError;
-import se.leap.openvpn.LaunchVPN;
-import se.leap.openvpn.OpenVpnManagementThread;
-import se.leap.openvpn.OpenVpnService;
-import se.leap.openvpn.OpenVpnService.LocalBinder;
-import se.leap.openvpn.ProfileManager;
-import se.leap.openvpn.VpnProfile;
+import de.blinkt.openvpn.activities.DisconnectVPN;
+import de.blinkt.openvpn.core.ConfigParser;
+import de.blinkt.openvpn.core.ConfigParser.ConfigParseError;
+import de.blinkt.openvpn.LaunchVPN;
+import de.blinkt.openvpn.core.OpenVpnManagementThread;
+import de.blinkt.openvpn.core.OpenVpnService;
+import de.blinkt.openvpn.core.OpenVpnService.LocalBinder;
+import de.blinkt.openvpn.core.ProfileManager;
+import de.blinkt.openvpn.VpnProfile;
import android.app.Activity;
import android.app.IntentService;
import android.content.ComponentName;
@@ -56,7 +57,7 @@ import android.util.Log;
* Internet Proxy connection. Connections are started, stopped, and queried through
* this IntentService.
* Contains logic for parsing eip-service.json from the provider, configuring and selecting
- * gateways, and controlling {@link .openvpn.OpenVpnService} connections.
+ * gateways, and controlling {@link de.blinkt.openvpn.core.OpenVpnService} connections.
*
* @author Sean Leonard <meanderingcode@aetherislands.net>
*/
@@ -68,6 +69,7 @@ public final class EIP extends IntentService {
public final static String ACTION_UPDATE_EIP_SERVICE = "se.leap.bitmaskclient.UPDATE_EIP_SERVICE";
public final static String ACTION_IS_EIP_RUNNING = "se.leap.bitmaskclient.IS_RUNNING";
public final static String EIP_NOTIFICATION = "EIP_NOTIFICATION";
+ public final static String STATUS = "eip status";
public final static String ALLOWED_ANON = "allow_anonymous";
public final static String CERTIFICATE = "cert";
public final static String PRIVATE_KEY = "private_key";
@@ -136,7 +138,7 @@ public final class EIP extends IntentService {
*/
private boolean retreiveVpnService() {
Intent bindIntent = new Intent(this,OpenVpnService.class);
- bindIntent.setAction(OpenVpnService.RETRIEVE_SERVICE);
+ bindIntent.setAction(OpenVpnService.START_SERVICE);
return bindService(bindIntent, mVpnServiceConn, BIND_AUTO_CREATE);
}
@@ -200,8 +202,9 @@ public final class EIP extends IntentService {
Bundle resultData = new Bundle();
resultData.putString(REQUEST_TAG, ACTION_IS_EIP_RUNNING);
int resultCode = Activity.RESULT_CANCELED;
+ boolean is_connected = getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).getString(STATUS, "").equalsIgnoreCase("LEVEL_CONNECTED");
if (mBound) {
- resultCode = (mVpnService.isRunning()) ? Activity.RESULT_OK : Activity.RESULT_CANCELED;
+ resultCode = (is_connected) ? Activity.RESULT_OK : Activity.RESULT_CANCELED;
if (mReceiver != null){
mReceiver.send(resultCode, resultData);
@@ -215,12 +218,7 @@ public final class EIP extends IntentService {
// TODO Auto-generated catch block
e.printStackTrace();
}
- boolean running = false;
- try {
- running = mVpnService.isRunning();
- } catch (NullPointerException e){
- e.printStackTrace();
- }
+ boolean running = is_connected;
if (retrieved_vpn_service && running && mReceiver != null){
mReceiver.send(Activity.RESULT_OK, resultData);
@@ -243,6 +241,7 @@ public final class EIP extends IntentService {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(LaunchVPN.EXTRA_KEY, activeGateway.mVpnProfile.getUUID().toString() );
intent.putExtra(LaunchVPN.EXTRA_NAME, activeGateway.mVpnProfile.getName() );
+ intent.putExtra(LaunchVPN.EXTRA_HIDELOG, true);
intent.putExtra(RECEIVER_TAG, mReceiver);
startActivity(intent);
mPending = ACTION_START_EIP;
@@ -255,8 +254,11 @@ public final class EIP extends IntentService {
private void stopEIP() {
if (mBound)
mVpnService.onRevoke();
- else
- OpenVpnManagementThread.stopOpenVPN();
+ else if(getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).getString(STATUS, "").startsWith("LEVEL_CONNECT")){
+ Intent disconnect_vpn = new Intent(this, DisconnectVPN.class);
+ disconnect_vpn.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(disconnect_vpn);
+ }
if (mReceiver != null){
Bundle resultData = new Bundle();
@@ -536,7 +538,10 @@ public final class EIP extends IntentService {
// TODO Auto-generated catch block
e.printStackTrace();
}
-
+
+ // We are always client, because the ifconfig will be received by a needed command
+ options.put("client", null);
+
try {
arg.add(remote);
arg.add(mGateway.getString(remote));
@@ -551,22 +556,22 @@ public final class EIP extends IntentService {
- try {
-
- arg.add(location_key);
- String locationText = "";
- locationText = eipDefinition.getJSONObject(locations).getJSONObject(mGateway.getString(location_key)).getString("name");
- arg.add(locationText);
+ // try {
+ // arg.add(location_key);
+ // String locationText = "";
+ // locationText = eipDefinition.getJSONObject(locations).getJSONObject(mGateway.getString(location_key)).getString("name");
+ // arg.add(locationText);
+ // Log.d(TAG, "location = " + locationText);
- } catch (JSONException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- args.add((Vector<String>) arg.clone());
- options.put("location", (Vector<Vector<String>>) args.clone() );
+ // } catch (JSONException e) {
+ // // TODO Auto-generated catch block
+ // e.printStackTrace();
+ // }
+ // args.add((Vector<String>) arg.clone());
+ // options.put("location", (Vector<Vector<String>>) args.clone() );
- arg.clear();
- args.clear();
+ // arg.clear();
+ // args.clear();
JSONArray protocolsJSON = null;
arg.add("proto");
try {
diff --git a/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java b/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java
index 4341c9dc..446ba1d9 100644
--- a/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java
@@ -1,9 +1,10 @@
package se.leap.bitmaskclient;
import se.leap.bitmaskclient.R;
-import se.leap.openvpn.LogWindow;
-import se.leap.openvpn.OpenVPN;
-import se.leap.openvpn.OpenVPN.StateListener;
+import de.blinkt.openvpn.activities.LogWindow;
+import de.blinkt.openvpn.core.VpnStatus;
+import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus;
+import de.blinkt.openvpn.core.VpnStatus.StateListener;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Fragment;
@@ -94,7 +95,7 @@ public class EipServiceFragment extends Fragment implements StateListener, OnChe
public void onResume() {
super.onResume();
- OpenVPN.addStateListener(this);
+ VpnStatus.addStateListener(this);
if(set_switch_off) {
eipSwitch.setChecked(false);
set_switch_off = false;
@@ -109,7 +110,7 @@ public class EipServiceFragment extends Fragment implements StateListener, OnChe
public void onPause() {
super.onPause();
- OpenVPN.removeStateListener(this);
+ VpnStatus.removeStateListener(this);
}
@Override
@@ -203,7 +204,7 @@ public class EipServiceFragment extends Fragment implements StateListener, OnChe
}
@Override
- public void updateState(final String state, final String logmessage, final int localizedResId) {
+ public void updateState(final String state, final String logmessage, final int localizedResId, final ConnectionStatus level) {
// Note: "states" are not organized anywhere...collected state strings:
// NOPROCESS,NONETWORK,BYTECOUNT,AUTH_FAILED + some parsing thing ( WAIT(?),AUTH,GET_CONFIG,ASSIGN_IP,CONNECTED,SIGINT )
getActivity().runOnUiThread(new Runnable() {
@@ -214,26 +215,19 @@ public class EipServiceFragment extends Fragment implements StateListener, OnChe
boolean switchState = true;
String statusMessage = "";
String prefix = getString(localizedResId);
- if (state.equals("CONNECTED")){
-
+ if (level == ConnectionStatus.LEVEL_CONNECTED){
statusMessage = getString(R.string.eip_state_connected);
getActivity().findViewById(R.id.eipProgress).setVisibility(View.GONE);
mEipStartPending = false;
- } else if (state.equals("BYTECOUNT")) {
- statusMessage = getString(R.string.eip_state_connected); getActivity().findViewById(R.id.eipProgress).setVisibility(View.GONE);
- mEipStartPending = false;
-
- } else if ( (state.equals("NOPROCESS") && !mEipStartPending ) || state.equals("EXITING") && !mEipStartPending || state.equals("FATAL")) {
+ } else if ( level == ConnectionStatus.LEVEL_NONETWORK || level == ConnectionStatus.LEVEL_NOTCONNECTED || level == ConnectionStatus.LEVEL_AUTH_FAILED) {
statusMessage = getString(R.string.eip_state_not_connected);
getActivity().findViewById(R.id.eipProgress).setVisibility(View.GONE);
mEipStartPending = false;
switchState = false;
- } else if (state.equals("NOPROCESS")){
- statusMessage = logmessage;
- } else if (state.equals("ASSIGN_IP")){ //don't show assigning message in eipStatus
- statusMessage = (String) eipStatus.getText();
- }
- else {
+ } else if (level == ConnectionStatus.LEVEL_CONNECTING_SERVER_REPLIED) {
+ if(state.equals("AUTH") || state.equals("GET_CONFIG"))
+ statusMessage = prefix + " " + logmessage;
+ } else if (level == ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET) {
statusMessage = prefix + " " + logmessage;
}
@@ -278,6 +272,7 @@ public class EipServiceFragment extends Fragment implements StateListener, OnChe
switch (resultCode){
case Activity.RESULT_OK:
checked = true;
+ eipFragment.findViewById(R.id.eipProgress).setVisibility(View.VISIBLE);
break;
case Activity.RESULT_CANCELED:
checked = false;
diff --git a/app/src/main/java/se/leap/bitmaskclient/EipStatusReceiver.java b/app/src/main/java/se/leap/bitmaskclient/EipStatusReceiver.java
new file mode 100644
index 00000000..8793cf36
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/EipStatusReceiver.java
@@ -0,0 +1,17 @@
+package se.leap.bitmaskclient;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+
+public class EipStatusReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals("de.blinkt.openvpn.VPN_STATUS")) {
+ context.getSharedPreferences(Dashboard.SHARED_PREFERENCES, Context.MODE_PRIVATE).edit().putString(EIP.STATUS, intent.getStringExtra("status")).commit();
+ }
+ }
+}
diff --git a/app/src/main/java/se/leap/openvpn/CIDRIP.java b/app/src/main/java/se/leap/openvpn/CIDRIP.java
deleted file mode 100644
index 8c4b6709..00000000
--- a/app/src/main/java/se/leap/openvpn/CIDRIP.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package se.leap.openvpn;
-
-class CIDRIP{
- String mIp;
- int len;
- public CIDRIP(String ip, String mask){
- mIp=ip;
- long netmask=getInt(mask);
-
- // Add 33. bit to ensure the loop terminates
- netmask += 1l << 32;
-
- int lenZeros = 0;
- while((netmask & 0x1) == 0) {
- lenZeros++;
- netmask = netmask >> 1;
- }
- // Check if rest of netmask is only 1s
- if(netmask != (0x1ffffffffl >> lenZeros)) {
- // Asume no CIDR, set /32
- len=32;
- } else {
- len =32 -lenZeros;
- }
-
- }
- @Override
- public String toString() {
- return String.format("%s/%d",mIp,len);
- }
-
- public boolean normalise(){
- long ip=getInt(mIp);
-
- long newip = ip & (0xffffffffl << (32 -len));
- if (newip != ip){
- mIp = String.format("%d.%d.%d.%d", (newip & 0xff000000) >> 24,(newip & 0xff0000) >> 16, (newip & 0xff00) >> 8 ,newip & 0xff);
- return true;
- } else {
- return false;
- }
- }
- static long getInt(String ipaddr) {
- String[] ipt = ipaddr.split("\\.");
- long ip=0;
-
- ip += Long.parseLong(ipt[0])<< 24;
- ip += Integer.parseInt(ipt[1])<< 16;
- ip += Integer.parseInt(ipt[2])<< 8;
- ip += Integer.parseInt(ipt[3]);
-
- return ip;
- }
- public long getInt() {
- return getInt(mIp);
- }
-
-} \ No newline at end of file
diff --git a/app/src/main/java/se/leap/openvpn/LICENSE.txt b/app/src/main/java/se/leap/openvpn/LICENSE.txt
deleted file mode 100644
index d897edea..00000000
--- a/app/src/main/java/se/leap/openvpn/LICENSE.txt
+++ /dev/null
@@ -1,24 +0,0 @@
-License for OpenVPN for Android. Please note that the thirdparty libraries/executables may have other license (OpenVPN, lzo, OpenSSL, Google Breakpad)
-
-Copyright (c) 2012-2013, Arne Schwabe
- All rights reserved.
-
-If you need a non GPLv2 license of the source please contact me.
-
-This program is free software; you can redistribute it and/or
-modify it under the terms of the GNU General Public License
-as published by the Free Software Foundation; either version 2
-of the License, or (at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-
-In addition, as a special exception, the copyright holders give
-permission to link the code of portions of this program with the
-OpenSSL library.
diff --git a/app/src/main/java/se/leap/openvpn/LaunchVPN.java b/app/src/main/java/se/leap/openvpn/LaunchVPN.java
deleted file mode 100644
index 89f2d372..00000000
--- a/app/src/main/java/se/leap/openvpn/LaunchVPN.java
+++ /dev/null
@@ -1,385 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package se.leap.openvpn;
-
-import java.io.IOException;
-import java.util.Collection;
-import java.util.Vector;
-
-import se.leap.bitmaskclient.ConfigHelper;
-import se.leap.bitmaskclient.EIP;
-import se.leap.bitmaskclient.R;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.ListActivity;
-import android.content.ActivityNotFoundException;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.net.VpnService;
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.os.ResultReceiver;
-import android.preference.PreferenceManager;
-import android.text.InputType;
-import android.text.method.PasswordTransformationMethod;
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.ArrayAdapter;
-import android.widget.EditText;
-import android.widget.ListView;
-import android.widget.TextView;
-
-/**
- * This Activity actually handles two stages of a launcher shortcut's life cycle.
- *
- * 1. Your application offers to provide shortcuts to the launcher. When
- * the user installs a shortcut, an activity within your application
- * generates the actual shortcut and returns it to the launcher, where it
- * is shown to the user as an icon.
- *
- * 2. Any time the user clicks on an installed shortcut, an intent is sent.
- * Typically this would then be handled as necessary by an activity within
- * your application.
- *
- * We handle stage 1 (creating a shortcut) by simply sending back the information (in the form
- * of an {@link android.content.Intent} that the launcher will use to create the shortcut.
- *
- * You can also implement this in an interactive way, by having your activity actually present
- * UI for the user to select the specific nature of the shortcut, such as a contact, picture, URL,
- * media item, or action.
- *
- * We handle stage 2 (responding to a shortcut) in this sample by simply displaying the contents
- * of the incoming {@link android.content.Intent}.
- *
- * In a real application, you would probably use the shortcut intent to display specific content
- * or start a particular operation.
- */
-public class LaunchVPN extends ListActivity implements OnItemClickListener {
-
- public static final String EXTRA_KEY = "se.leap.openvpn.shortcutProfileUUID";
- public static final String EXTRA_NAME = "se.leap.openvpn.shortcutProfileName";
- public static final String EXTRA_HIDELOG = "se.leap.openvpn.showNoLogWindow";;
-
- public static final int START_VPN_PROFILE= 70;
-
- // Dashboard, maybe more, want to know!
- private ResultReceiver mReceiver;
-
- private ProfileManager mPM;
- private VpnProfile mSelectedProfile;
- private boolean mhideLog=false;
-
- private boolean mCmfixed=false;
-
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- mPM =ProfileManager.getInstance(this);
-
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- // Resolve the intent
-
- final Intent intent = getIntent();
- final String action = intent.getAction();
-
- // If something wants feedback, they sent us a Receiver
- mReceiver = intent.getParcelableExtra(EIP.RECEIVER_TAG);
-
- // If the intent is a request to create a shortcut, we'll do that and exit
-
-
- if(Intent.ACTION_MAIN.equals(action)) {
- // we got called to be the starting point, most likely a shortcut
- String shortcutUUID = intent.getStringExtra( EXTRA_KEY);
- String shortcutName = intent.getStringExtra( EXTRA_NAME);
- mhideLog = intent.getBooleanExtra(EXTRA_HIDELOG, false);
-
- VpnProfile profileToConnect = ProfileManager.get(shortcutUUID);
- if(shortcutName != null && profileToConnect ==null)
- profileToConnect = ProfileManager.getInstance(this).getProfileByName(shortcutName);
-
- if(profileToConnect ==null) {
- OpenVPN.logError(R.string.shortcut_profile_notfound);
- // show Log window to display error
- showLogWindow();
- finish();
- return;
- }
-
- mSelectedProfile = profileToConnect;
- launchVPN();
-
- } else if (Intent.ACTION_CREATE_SHORTCUT.equals(action)) {
- createListView();
- }
- }
-
- private void createListView() {
- ListView lv = getListView();
- //lv.setTextFilterEnabled(true);
-
- Collection<VpnProfile> vpnlist = mPM.getProfiles();
-
- Vector<String> vpnnames=new Vector<String>();
- for (VpnProfile vpnProfile : vpnlist) {
- vpnnames.add(vpnProfile.mName);
- }
-
-
-
- ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,vpnnames);
- lv.setAdapter(adapter);
-
- lv.setOnItemClickListener(this);
- }
-
- /**
- * This function creates a shortcut and returns it to the caller. There are actually two
- * intents that you will send back.
- *
- * The first intent serves as a container for the shortcut and is returned to the launcher by
- * setResult(). This intent must contain three fields:
- *
- * <ul>
- * <li>{@link android.content.Intent#EXTRA_SHORTCUT_INTENT} The shortcut intent.</li>
- * <li>{@link android.content.Intent#EXTRA_SHORTCUT_NAME} The text that will be displayed with
- * the shortcut.</li>
- * <li>{@link android.content.Intent#EXTRA_SHORTCUT_ICON} The shortcut's icon, if provided as a
- * bitmap, <i>or</i> {@link android.content.Intent#EXTRA_SHORTCUT_ICON_RESOURCE} if provided as
- * a drawable resource.</li>
- * </ul>
- *
- * If you use a simple drawable resource, note that you must wrapper it using
- * {@link android.content.Intent.ShortcutIconResource}, as shown below. This is required so
- * that the launcher can access resources that are stored in your application's .apk file. If
- * you return a bitmap, such as a thumbnail, you can simply put the bitmap into the extras
- * bundle using {@link android.content.Intent#EXTRA_SHORTCUT_ICON}.
- *
- * The shortcut intent can be any intent that you wish the launcher to send, when the user
- * clicks on the shortcut. Typically this will be {@link android.content.Intent#ACTION_VIEW}
- * with an appropriate Uri for your content, but any Intent will work here as long as it
- * triggers the desired action within your Activity.
- * @param profile
- */
- private void setupShortcut(VpnProfile profile) {
- // First, set up the shortcut intent. For this example, we simply create an intent that
- // will bring us directly back to this activity. A more typical implementation would use a
- // data Uri in order to display a more specific result, or a custom action in order to
- // launch a specific operation.
-
- Intent shortcutIntent = new Intent(Intent.ACTION_MAIN);
- shortcutIntent.setClass(this, LaunchVPN.class);
- shortcutIntent.putExtra(EXTRA_KEY,profile.getUUID().toString());
-
- // Then, set up the container intent (the response to the caller)
-
- Intent intent = new Intent();
- intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
- intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, profile.getName());
- Parcelable iconResource = Intent.ShortcutIconResource.fromContext(
- this, R.drawable.icon);
- intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource);
-
- // Now, return the result to the launcher
-
- setResult(RESULT_OK, intent);
- }
-
-
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position,
- long id) {
- String profilename = ((TextView) view).getText().toString();
-
- VpnProfile profile = mPM.getProfileByName(profilename);
-
- // if (Intent.ACTION_CREATE_SHORTCUT.equals(action)) {
- setupShortcut(profile);
- finish();
- return;
- // }
-
- }
-
-
-
- private void askForPW(final int type) {
-
- final EditText entry = new EditText(this);
- entry.setSingleLine();
- entry.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
- entry.setTransformationMethod(new PasswordTransformationMethod());
-
- AlertDialog.Builder dialog = new AlertDialog.Builder(this);
- dialog.setTitle("Need " + getString(type));
- dialog.setMessage("Enter the password for profile " + mSelectedProfile.mName);
- dialog.setView(entry);
-
- dialog.setPositiveButton(android.R.string.ok,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- String pw = entry.getText().toString();
- if(type == R.string.password) {
- mSelectedProfile.mTransientPW = pw;
- } else {
- mSelectedProfile.mTransientPCKS12PW = pw;
- }
- onActivityResult(START_VPN_PROFILE, Activity.RESULT_OK, null);
-
- }
-
- });
- dialog.setNegativeButton(android.R.string.cancel,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- finish();
- }
- });
-
- dialog.create().show();
-
- }
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
-
- if(requestCode==START_VPN_PROFILE) {
- if(resultCode == Activity.RESULT_OK) {
- int needpw = mSelectedProfile.needUserPWInput();
- if(needpw !=0) {
- askForPW(needpw);
- } else {
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
- boolean showlogwindow = prefs.getBoolean("showlogwindow", false);
-
- if(!mhideLog && showlogwindow)
- showLogWindow();
- new startOpenVpnThread().start();
- }
- } else if (resultCode == Activity.RESULT_CANCELED) {
- // User does not want us to start, so we just vanish (well, now we tell our receiver, then vanish)
- Bundle resultData = new Bundle();
- // For now, nothing else is calling, so this "request" string is good enough
- resultData.putString(EIP.REQUEST_TAG, EIP.ACTION_START_EIP);
- mReceiver.send(RESULT_CANCELED, resultData);
- finish();
- }
- }
- }
- void showLogWindow() {
-
- Intent startLW = new Intent(getBaseContext(),LogWindow.class);
- startLW.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
- startActivity(startLW);
-
- }
-
- void showConfigErrorDialog(int vpnok) {
- AlertDialog.Builder d = new AlertDialog.Builder(this);
- d.setTitle(R.string.config_error_found);
- d.setMessage(vpnok);
- d.setPositiveButton(android.R.string.ok, new OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- finish();
-
- }
- });
- d.show();
- }
-
- void launchVPN () {
- int vpnok = mSelectedProfile.checkProfile(this);
- if(vpnok!= R.string.no_error_found) {
- showConfigErrorDialog(vpnok);
- return;
- }
-
- Intent intent = VpnService.prepare(this);
- // Check if we want to fix /dev/tun
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
- boolean usecm9fix = prefs.getBoolean("useCM9Fix", false);
- boolean loadTunModule = prefs.getBoolean("loadTunModule", false);
-
- if(loadTunModule)
- execeuteSUcmd("insmod /system/lib/modules/tun.ko");
-
- if(usecm9fix && !mCmfixed ) {
- execeuteSUcmd("chown system /dev/tun");
- }
-
-
- if (intent != null) {
- // Start the query
- try {
- startActivityForResult(intent, START_VPN_PROFILE);
- } catch (ActivityNotFoundException ane) {
- // Shame on you Sony! At least one user reported that
- // an official Sony Xperia Arc S image triggers this exception
- OpenVPN.logError(R.string.no_vpn_support_image);
- showLogWindow();
- }
- } else {
- onActivityResult(START_VPN_PROFILE, Activity.RESULT_OK, null);
- }
-
- }
-
- private void execeuteSUcmd(String command) {
- ProcessBuilder pb = new ProcessBuilder(new String[] {"su","-c",command});
- try {
- Process p = pb.start();
- int ret = p.waitFor();
- if(ret ==0)
- mCmfixed=true;
- } catch (InterruptedException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- private class startOpenVpnThread extends Thread {
-
- @Override
- public void run() {
- VPNLaunchHelper.startOpenVpn(mSelectedProfile, getBaseContext());
- // Tell whom-it-may-concern that we started VPN
- Bundle resultData = new Bundle();
- // For now, nothing else is calling, so this "request" string is good enough
- resultData.putString(EIP.REQUEST_TAG, EIP.ACTION_START_EIP);
- mReceiver.send(RESULT_OK, resultData);
- finish();
-
- }
-
- }
-
-
-}
diff --git a/app/src/main/java/se/leap/openvpn/LogWindow.java b/app/src/main/java/se/leap/openvpn/LogWindow.java
deleted file mode 100644
index b87c4999..00000000
--- a/app/src/main/java/se/leap/openvpn/LogWindow.java
+++ /dev/null
@@ -1,340 +0,0 @@
-package se.leap.openvpn;
-
-import java.util.Vector;
-
-import se.leap.bitmaskclient.R;
-
-import android.app.AlertDialog;
-import android.app.AlertDialog.Builder;
-import android.app.ListActivity;
-import android.content.ClipData;
-import android.content.ClipboardManager;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
-import android.content.Intent;
-import android.database.DataSetObserver;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Handler.Callback;
-import android.os.Message;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemLongClickListener;
-import android.widget.ListAdapter;
-import android.widget.ListView;
-import android.widget.TextView;
-import android.widget.Toast;
-import se.leap.openvpn.OpenVPN.LogItem;
-import se.leap.openvpn.OpenVPN.LogListener;
-import se.leap.openvpn.OpenVPN.StateListener;
-
-public class LogWindow extends ListActivity implements StateListener {
- private static final int START_VPN_CONFIG = 0;
- private String[] mBconfig=null;
-
-
- class LogWindowListAdapter implements ListAdapter, LogListener, Callback {
-
- private static final int MESSAGE_NEWLOG = 0;
-
- private static final int MESSAGE_CLEARLOG = 1;
-
- private Vector<String> myEntries=new Vector<String>();
-
- private Handler mHandler;
-
- private Vector<DataSetObserver> observers=new Vector<DataSetObserver>();
-
-
- public LogWindowListAdapter() {
- initLogBuffer();
-
- if (mHandler == null) {
- mHandler = new Handler(this);
- }
-
- OpenVPN.addLogListener(this);
- }
-
-
-
- private void initLogBuffer() {
- myEntries.clear();
- for (LogItem litem : OpenVPN.getlogbuffer()) {
- myEntries.add(litem.getString(getContext()));
- }
- }
-
- String getLogStr() {
- String str = "";
- for(String entry:myEntries) {
- str+=entry + '\n';
- }
- return str;
- }
-
-
- private void shareLog() {
- Intent shareIntent = new Intent(Intent.ACTION_SEND);
- shareIntent.putExtra(Intent.EXTRA_TEXT, getLogStr());
- shareIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.bitmask_openvpn_log_file));
- shareIntent.setType("text/plain");
- startActivity(Intent.createChooser(shareIntent, "Send Logfile"));
- }
-
- @Override
- public void registerDataSetObserver(DataSetObserver observer) {
- observers.add(observer);
-
- }
-
- @Override
- public void unregisterDataSetObserver(DataSetObserver observer) {
- observers.remove(observer);
- }
-
- @Override
- public int getCount() {
- return myEntries.size();
- }
-
- @Override
- public Object getItem(int position) {
- return myEntries.get(position);
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- public boolean hasStableIds() {
- return true;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- TextView v;
- if(convertView==null)
- v = new TextView(getBaseContext());
- else
- v = (TextView) convertView;
- v.setText(myEntries.get(position));
- return v;
- }
-
- @Override
- public int getItemViewType(int position) {
- return 0;
- }
-
- @Override
- public int getViewTypeCount() {
- return 1;
- }
-
- @Override
- public boolean isEmpty() {
- return myEntries.isEmpty();
-
- }
-
- @Override
- public boolean areAllItemsEnabled() {
- return true;
- }
-
- @Override
- public boolean isEnabled(int position) {
- return true;
- }
-
- @Override
- public void newLog(LogItem logmessage) {
- Message msg = Message.obtain();
- msg.what=MESSAGE_NEWLOG;
- Bundle mbundle=new Bundle();
- mbundle.putString("logmessage", logmessage.getString(getBaseContext()));
- msg.setData(mbundle);
- mHandler.sendMessage(msg);
- }
-
- @Override
- public boolean handleMessage(Message msg) {
- // We have been called
- if(msg.what==MESSAGE_NEWLOG) {
-
- String logmessage = msg.getData().getString("logmessage");
- myEntries.add(logmessage);
-
- for (DataSetObserver observer : observers) {
- observer.onChanged();
- }
- } else if (msg.what == MESSAGE_CLEARLOG) {
- initLogBuffer();
- for (DataSetObserver observer : observers) {
- observer.onInvalidated();
- }
- }
-
- return true;
- }
-
- void clearLog() {
- // Actually is probably called from GUI Thread as result of the user
- // pressing a button. But better safe than sorry
- OpenVPN.clearLog();
- OpenVPN.logMessage(0,"","Log cleared.");
- mHandler.sendEmptyMessage(MESSAGE_CLEARLOG);
- }
- }
-
-
-
- private LogWindowListAdapter ladapter;
- private TextView mSpeedView;
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- if(item.getItemId()==R.id.clearlog) {
- ladapter.clearLog();
- return true;
- } else if(item.getItemId()==R.id.cancel){
- 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, null);
- builder.setPositiveButton(android.R.string.yes, new OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- ProfileManager.setConntectedVpnProfileDisconnected(getApplicationContext());
- OpenVpnManagementThread.stopOpenVPN();
- }
- });
-
- builder.show();
- return true;
- } else if(item.getItemId()==R.id.info) {
- if(mBconfig==null)
- OpenVPN.triggerLogBuilderConfig();
-
- } else if(item.getItemId()==R.id.send) {
- ladapter.shareLog();
- }
-
- return super.onOptionsItemSelected(item);
-
- }
-
- protected Context getContext() {
- return this;
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.logmenu, menu);
- return true;
- }
-
-
- @Override
- protected void onResume() {
- super.onResume();
- OpenVPN.addStateListener(this);
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode == START_VPN_CONFIG && resultCode==RESULT_OK) {
- String configuredVPN = data.getStringExtra(VpnProfile.EXTRA_PROFILEUUID);
-
- final VpnProfile profile = ProfileManager.get(configuredVPN);
- ProfileManager.getInstance(this).saveProfile(this, profile);
- // Name could be modified, reset List adapter
-
- AlertDialog.Builder dialog = new AlertDialog.Builder(this);
- dialog.setTitle(R.string.configuration_changed);
- dialog.setMessage(R.string.restart_vpn_after_change);
-
-
- dialog.setPositiveButton(R.string.restart,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- Intent intent = new Intent(getBaseContext(), LaunchVPN.class);
- intent.putExtra(LaunchVPN.EXTRA_KEY, profile.getUUIDString());
- intent.setAction(Intent.ACTION_MAIN);
- startActivity(intent);
- }
-
-
- });
- dialog.setNegativeButton(R.string.ignore, null);
- dialog.create().show();
- }
- super.onActivityResult(requestCode, resultCode, data);
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- OpenVPN.removeStateListener(this);
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- setContentView(R.layout.logwindow);
- ListView lv = getListView();
-
- lv.setOnItemLongClickListener(new OnItemLongClickListener() {
-
- @Override
- public boolean onItemLongClick(AdapterView<?> parent, View view,
- int position, long id) {
- ClipboardManager clipboard = (ClipboardManager)
- getSystemService(Context.CLIPBOARD_SERVICE);
- ClipData clip = ClipData.newPlainText("Log Entry",((TextView) view).getText());
- clipboard.setPrimaryClip(clip);
- Toast.makeText(getBaseContext(), R.string.copied_entry, Toast.LENGTH_SHORT).show();
- return true;
- }
- });
-
- ladapter = new LogWindowListAdapter();
- lv.setAdapter(ladapter);
-
- mSpeedView = (TextView) findViewById(R.id.speed);
- }
-
- @Override
- public void updateState(final String status,final String logmessage, final int resid) {
- runOnUiThread(new Runnable() {
-
- @Override
- public void run() {
- String prefix=getString(resid) + ":";
- if (status.equals("BYTECOUNT") || status.equals("NOPROCESS") )
- prefix="";
- mSpeedView.setText(prefix + logmessage);
- }
- });
-
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- OpenVPN.removeLogListener(ladapter);
- }
-
-}
diff --git a/app/src/main/java/se/leap/openvpn/NetworkSateReceiver.java b/app/src/main/java/se/leap/openvpn/NetworkSateReceiver.java
deleted file mode 100644
index 777402b4..00000000
--- a/app/src/main/java/se/leap/openvpn/NetworkSateReceiver.java
+++ /dev/null
@@ -1,86 +0,0 @@
-package se.leap.openvpn;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.net.NetworkInfo.State;
-import android.preference.PreferenceManager;
-import se.leap.bitmaskclient.R;
-
-public class NetworkSateReceiver extends BroadcastReceiver {
- private int lastNetwork=-1;
- private OpenVpnManagementThread mManangement;
-
- private String lastStateMsg=null;
-
- public NetworkSateReceiver(OpenVpnManagementThread managementThread) {
- super();
- mManangement = managementThread;
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- NetworkInfo networkInfo = getCurrentNetworkInfo(context);
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
- boolean sendusr1 = prefs.getBoolean("netchangereconnect", true);
-
- String netstatestring;
- if(networkInfo==null)
- netstatestring = "not connected";
- else {
- String subtype = networkInfo.getSubtypeName();
- if(subtype==null)
- subtype = "";
- String extrainfo = networkInfo.getExtraInfo();
- if(extrainfo==null)
- extrainfo="";
-
- /*
- if(networkInfo.getType()==android.net.ConnectivityManager.TYPE_WIFI) {
- WifiManager wifiMgr = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
- WifiInfo wifiinfo = wifiMgr.getConnectionInfo();
- extrainfo+=wifiinfo.getBSSID();
-
- subtype += wifiinfo.getNetworkId();
- }*/
-
-
-
- netstatestring = String.format("%2$s %4$s to %1$s %3$s",networkInfo.getTypeName(),
- networkInfo.getDetailedState(),extrainfo,subtype );
- }
-
-
-
- if(networkInfo!=null && networkInfo.getState() == State.CONNECTED) {
- int newnet = networkInfo.getType();
-
- if(sendusr1 && lastNetwork!=newnet)
- mManangement.reconnect();
-
- lastNetwork = newnet;
- } else if (networkInfo==null) {
- // Not connected, stop openvpn, set last connected network to no network
- lastNetwork=-1;
- if(sendusr1)
- mManangement.signalusr1();
- }
-
- if(!netstatestring.equals(lastStateMsg))
- OpenVPN.logInfo(R.string.netstatus, netstatestring);
- lastStateMsg=netstatestring;
-
- }
-
- private NetworkInfo getCurrentNetworkInfo(Context context) {
- ConnectivityManager conn = (ConnectivityManager)
- context.getSystemService(Context.CONNECTIVITY_SERVICE);
-
- NetworkInfo networkInfo = conn.getActiveNetworkInfo();
- return networkInfo;
- }
-
-}
diff --git a/app/src/main/java/se/leap/openvpn/OpenVPN.java b/app/src/main/java/se/leap/openvpn/OpenVPN.java
deleted file mode 100644
index 8acdc423..00000000
--- a/app/src/main/java/se/leap/openvpn/OpenVPN.java
+++ /dev/null
@@ -1,250 +0,0 @@
-package se.leap.openvpn;
-
-import java.util.LinkedList;
-import java.util.Locale;
-import java.util.Vector;
-
-import se.leap.bitmaskclient.R;
-
-
-import android.content.Context;
-import android.os.Build;
-import android.util.Log;
-
-public class OpenVPN {
-
-
- public static LinkedList<LogItem> logbuffer;
-
- private static Vector<LogListener> logListener;
- private static Vector<StateListener> stateListener;
- private static String[] mBconfig;
-
- private static String mLaststatemsg;
-
- private static String mLaststate;
-
- private static int mLastStateresid=R.string.state_noprocess;
- public static String TAG="se.leap.openvpn.OpenVPN";
-
- static {
- logbuffer = new LinkedList<LogItem>();
- logListener = new Vector<OpenVPN.LogListener>();
- stateListener = new Vector<OpenVPN.StateListener>();
- logInformation();
- }
-
- public static class LogItem {
- public static final int ERROR = 1;
- public static final int INFO = 2;
- public static final int VERBOSE = 3;
-
- private Object [] mArgs = null;
- private String mMessage = null;
- private int mRessourceId;
- // Default log priority
- int mLevel = INFO;
-
- public LogItem(int ressourceId, Object[] args) {
- mRessourceId = ressourceId;
- mArgs = args;
- }
-
-
- public LogItem(int loglevel,int ressourceId, Object[] args) {
- mRessourceId = ressourceId;
- mArgs = args;
- mLevel = loglevel;
- }
-
-
- public LogItem(String message) {
-
- mMessage = message;
- }
-
- public LogItem(int loglevel, String msg) {
- mLevel = loglevel;
- mMessage = msg;
- }
-
-
- public LogItem(int loglevel, int ressourceId) {
- mRessourceId =ressourceId;
- mLevel = loglevel;
- }
-
-
- public String getString(Context c) {
- if(mMessage !=null) {
- return mMessage;
- } else {
- if(c!=null) {
- if(mArgs == null)
- return c.getString(mRessourceId);
- else
- return c.getString(mRessourceId,mArgs);
- } else {
- String str = String.format(Locale.ENGLISH,"Log (no context) resid %d", mRessourceId);
- if(mArgs !=null)
- for(Object o:mArgs)
- str += "|" + o.toString();
- return str;
- }
- }
- }
- }
-
- private static final int MAXLOGENTRIES = 500;
-
-
- public static final String MANAGMENT_PREFIX = "M:";
-
-
-
-
-
-
- public interface LogListener {
- void newLog(LogItem logItem);
- }
-
- public interface StateListener {
- void updateState(String state, String logmessage, int localizedResId);
- }
-
- synchronized static void logMessage(int level,String prefix, String message)
- {
- newlogItem(new LogItem(prefix + message));
- Log.d("OpenVPN log item", message);
- }
-
- synchronized static void clearLog() {
- logbuffer.clear();
- logInformation();
- }
-
- private static void logInformation() {
-
- logInfo(R.string.mobile_info,Build.MODEL, Build.BOARD,Build.BRAND,Build.VERSION.SDK_INT);
- }
-
- public synchronized static void addLogListener(LogListener ll){
- logListener.add(ll);
- }
-
- public synchronized static void removeLogListener(LogListener ll) {
- logListener.remove(ll);
- }
-
-
- public synchronized static void addStateListener(StateListener sl){
- stateListener.add(sl);
- if(mLaststate!=null)
- sl.updateState(mLaststate, mLaststatemsg, mLastStateresid);
- }
-
- private static int getLocalizedState(String state){
- if (state.equals("CONNECTING"))
- return R.string.state_connecting;
- else if (state.equals("WAIT"))
- return R.string.state_wait;
- else if (state.equals("AUTH"))
- return R.string.state_auth;
- else if (state.equals("GET_CONFIG"))
- return R.string.state_get_config;
- else if (state.equals("ASSIGN_IP"))
- return R.string.state_assign_ip;
- else if (state.equals("ADD_ROUTES"))
- return R.string.state_add_routes;
- else if (state.equals("CONNECTED"))
- return R.string.state_connected;
- else if (state.equals("RECONNECTING"))
- return R.string.state_reconnecting;
- else if (state.equals("EXITING"))
- return R.string.state_exiting;
- else if (state.equals("RESOLVE"))
- return R.string.state_resolve;
- else if (state.equals("TCP_CONNECT"))
- return R.string.state_tcp_connect;
- else if (state.equals("FATAL"))
- return R.string.eip_state_not_connected;
- else
- return R.string.unknown_state;
-
- }
-
- public synchronized static void removeStateListener(StateListener sl) {
- stateListener.remove(sl);
- }
-
-
- synchronized public static LogItem[] getlogbuffer() {
-
- // The stoned way of java to return an array from a vector
- // brought to you by eclipse auto complete
- return (LogItem[]) logbuffer.toArray(new LogItem[logbuffer.size()]);
-
- }
- public static void logBuilderConfig(String[] bconfig) {
- mBconfig = bconfig;
- }
- public static void triggerLogBuilderConfig() {
- if(mBconfig==null) {
- logMessage(0, "", "No active interface");
- } else {
- for (String item : mBconfig) {
- logMessage(0, "", item);
- }
- }
-
- }
-
- public static void updateStateString (String state, String msg) {
- int rid = getLocalizedState(state);
- updateStateString(state, msg,rid);
- }
-
- public synchronized static void updateStateString(String state, String msg, int resid) {
- if (! "BYTECOUNT".equals(state)) {
- mLaststate= state;
- mLaststatemsg = msg;
- mLastStateresid = resid;
-
- for (StateListener sl : stateListener) {
- sl.updateState(state,msg,resid);
- }
- }
- }
-
- public static void logInfo(String message) {
- newlogItem(new LogItem(LogItem.INFO, message));
- }
-
- public static void logInfo(int ressourceId, Object... args) {
- newlogItem(new LogItem(LogItem.INFO, ressourceId, args));
- }
-
- private static void newlogItem(LogItem logItem) {
- logbuffer.addLast(logItem);
- if(logbuffer.size()>MAXLOGENTRIES)
- logbuffer.removeFirst();
-
- for (LogListener ll : logListener) {
- ll.newLog(logItem);
- }
- }
-
- public static void logError(String msg) {
- newlogItem(new LogItem(LogItem.ERROR, msg));
-
- }
-
- public static void logError(int ressourceId) {
- newlogItem(new LogItem(LogItem.ERROR, ressourceId));
- }
- public static void logError(int ressourceId, Object... args) {
- newlogItem(new LogItem(LogItem.ERROR, ressourceId,args));
- }
-
-}
diff --git a/app/src/main/java/se/leap/openvpn/OpenVPNThread.java b/app/src/main/java/se/leap/openvpn/OpenVPNThread.java
deleted file mode 100644
index ffd21732..00000000
--- a/app/src/main/java/se/leap/openvpn/OpenVPNThread.java
+++ /dev/null
@@ -1,130 +0,0 @@
-package se.leap.openvpn;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.LinkedList;
-
-import se.leap.bitmaskclient.R;
-
-import android.util.Log;
-import se.leap.openvpn.OpenVPN.LogItem;
-
-public class OpenVPNThread implements Runnable {
- private static final String DUMP_PATH_STRING = "Dump path: ";
- private static final String TAG = "OpenVPN";
- private String[] mArgv;
- private Process mProcess;
- private String mNativeDir;
- private OpenVpnService mService;
- private String mDumpPath;
-
- public OpenVPNThread(OpenVpnService service,String[] argv, String nativelibdir)
- {
- mArgv = argv;
- mNativeDir = nativelibdir;
- mService = service;
- }
-
- public void stopProcess() {
- mProcess.destroy();
- }
-
-
-
- @Override
- public void run() {
- try {
- Log.i(TAG, "Starting openvpn");
- startOpenVPNThreadArgs(mArgv);
- Log.i(TAG, "Giving up");
- } catch (Exception e) {
- e.printStackTrace();
- Log.e(TAG, "OpenVPNThread Got " + e.toString());
- } finally {
- int exitvalue = 0;
- try {
- exitvalue = mProcess.waitFor();
- } catch ( IllegalThreadStateException ite) {
- OpenVPN.logError("Illegal Thread state: " + ite.getLocalizedMessage());
- } catch (InterruptedException ie) {
- OpenVPN.logError("InterruptedException: " + ie.getLocalizedMessage());
- }
- if( exitvalue != 0)
- OpenVPN.logError("Process exited with exit value " + exitvalue);
-
-// OpenVPN.updateStateString("NOPROCESS","No process running.", R.string.state_noprocess); fixes bug #4565
- if(mDumpPath!=null) {
- try {
- BufferedWriter logout = new BufferedWriter(new FileWriter(mDumpPath + ".log"));
- for(LogItem li :OpenVPN.getlogbuffer()){
- logout.write(li.getString(null) + "\n");
- }
- logout.close();
- OpenVPN.logError(R.string.minidump_generated);
- } catch (IOException e) {
- OpenVPN.logError("Writing minidump log: " +e.getLocalizedMessage());
- }
- }
-
- mService.processDied();
- Log.i(TAG, "Exiting");
- }
- }
-
- private void startOpenVPNThreadArgs(String[] argv) {
- LinkedList<String> argvlist = new LinkedList<String>();
-
- for(String arg:argv)
- argvlist.add(arg);
-
- ProcessBuilder pb = new ProcessBuilder(argvlist);
- // Hack O rama
-
- // Hack until I find a good way to get the real library path
- String applibpath = argv[0].replace("/cache/" + VpnProfile.MINIVPN , "/lib");
-
- String lbpath = pb.environment().get("LD_LIBRARY_PATH");
- if(lbpath==null)
- lbpath = applibpath;
- else
- lbpath = lbpath + ":" + applibpath;
-
- if (!applibpath.equals(mNativeDir)) {
- lbpath = lbpath + ":" + mNativeDir;
- }
-
- pb.environment().put("LD_LIBRARY_PATH", lbpath);
- pb.redirectErrorStream(true);
- try {
- mProcess = pb.start();
- // Close the output, since we don't need it
- mProcess.getOutputStream().close();
- InputStream in = mProcess.getInputStream();
- BufferedReader br = new BufferedReader(new InputStreamReader(in));
-
- while(true) {
- String logline = br.readLine();
- if(logline==null)
- return;
-
- if (logline.startsWith(DUMP_PATH_STRING))
- mDumpPath = logline.substring(DUMP_PATH_STRING.length());
-
-
- OpenVPN.logMessage(0, "P:", logline);
- }
-
-
- } catch (IOException e) {
- OpenVPN.logMessage(0, "", "Error reading from output of OpenVPN process"+ e.getLocalizedMessage());
- e.printStackTrace();
- stopProcess();
- }
-
-
- }
-}
diff --git a/app/src/main/java/se/leap/openvpn/OpenVpnService.java b/app/src/main/java/se/leap/openvpn/OpenVpnService.java
deleted file mode 100644
index deec8518..00000000
--- a/app/src/main/java/se/leap/openvpn/OpenVpnService.java
+++ /dev/null
@@ -1,513 +0,0 @@
-package se.leap.openvpn;
-
-
-import android.annotation.TargetApi;
-import android.app.Activity;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.ConnectivityManager;
-import android.net.LocalServerSocket;
-import android.net.LocalSocket;
-import android.net.LocalSocketAddress;
-import android.net.VpnService;
-import android.os.Binder;
-import android.os.Build;
-import android.os.Handler.Callback;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.ParcelFileDescriptor;
-import java.io.IOException;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.Vector;
-import se.leap.bitmaskclient.Dashboard;
-import se.leap.bitmaskclient.R;
-import se.leap.openvpn.OpenVPN.StateListener;
-
-public class OpenVpnService extends VpnService implements StateListener, Callback {
- public static final String START_SERVICE = "se.leap.openvpn.START_SERVICE";
- public static final String RETRIEVE_SERVICE = "se.leap.openvpn.RETRIEVE_SERVICE";
-
- private Thread mProcessThread=null;
-
- private Vector<String> mDnslist=new Vector<String>();
-
- private VpnProfile mProfile;
-
- private String mDomain=null;
-
- private Vector<CIDRIP> mRoutes=new Vector<CIDRIP>();
- private Vector<String> mRoutesv6=new Vector<String>();
-
- private CIDRIP mLocalIP=null;
-
- private OpenVpnManagementThread mSocketManager;
-
- private Thread mSocketManagerThread;
- private int mMtu;
- private String mLocalIPv6=null;
- private NetworkSateReceiver mNetworkStateReceiver;
- private NotificationManager mNotificationManager;
-
- private boolean mDisplayBytecount=false;
-
- private boolean mStarting=false;
-
- private long mConnecttime;
-
-
- private static final int OPENVPN_STATUS = 1;
-
- public static final int PROTECT_FD = 0;
-
- private final IBinder mBinder = new LocalBinder();
-
- public class LocalBinder extends Binder {
- public OpenVpnService getService() {
- // Return this instance of LocalService so clients can call public methods
- return OpenVpnService.this;
- }
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- String action = intent.getAction();
- if( action !=null && (action.equals(START_SERVICE) || action.equals(RETRIEVE_SERVICE)) )
- return mBinder;
- else
- return super.onBind(intent);
- }
-
- @Override
- public void onRevoke() {
- OpenVpnManagementThread.stopOpenVPN();
- endVpnService();
- }
-
- // Similar to revoke but do not try to stop process
- public void processDied() {
- endVpnService();
- }
-
- private void endVpnService() {
- mProcessThread=null;
- OpenVPN.logBuilderConfig(null);
- ProfileManager.setConntectedVpnProfileDisconnected(this);
- if(!mStarting) {
- stopSelf();
- stopForeground(true);
- }
- }
-
- private void showNotification(String state, String msg, String tickerText, boolean lowpriority, long when, boolean persistant) {
- String ns = Context.NOTIFICATION_SERVICE;
- mNotificationManager = (NotificationManager) getSystemService(ns);
- int icon;
- if (state.equals("NOPROCESS") || state.equals("AUTH_FAILED") || state.equals("NONETWORK") || state.equals("EXITING")){
- icon = R.drawable.ic_vpn_disconnected;
- }else{
- icon = R.drawable.ic_stat_vpn;
- }
-
- android.app.Notification.Builder nbuilder = new Notification.Builder(this);
-
- nbuilder.setContentTitle(getString(R.string.notifcation_title,mProfile.mLocation));
- nbuilder.setContentText(msg);
- nbuilder.setOnlyAlertOnce(true);
- nbuilder.setOngoing(persistant);
- nbuilder.setContentIntent(getLogPendingIntent());
- nbuilder.setSmallIcon(icon);
- if(when !=0)
- nbuilder.setWhen(when);
-
-
- // Try to set the priority available since API 16 (Jellybean)
- jbNotificationExtras(lowpriority, nbuilder);
- if(tickerText!=null)
- nbuilder.setTicker(tickerText);
-
- @SuppressWarnings("deprecation")
- Notification notification = nbuilder.getNotification();
-
-
- mNotificationManager.notify(OPENVPN_STATUS, notification);
- }
-
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
- private void jbNotificationExtras(boolean lowpriority,
- android.app.Notification.Builder nbuilder) {
- try {
- if(lowpriority) {
- Method setpriority = nbuilder.getClass().getMethod("setPriority", int.class);
- // PRIORITY_MIN == -2
- setpriority.invoke(nbuilder, -2 );
-
- nbuilder.setUsesChronometer(true);
- /* PendingIntent cancelconnet=null;
-
- nbuilder.addAction(android.R.drawable.ic_menu_close_clear_cancel,
- getString(R.string.cancel_connection),cancelconnet); */
- }
-
- //ignore exception
- } catch (NoSuchMethodException nsm) {
- } catch (IllegalArgumentException e) {
- } catch (IllegalAccessException e) {
- } catch (InvocationTargetException e) {
- }
-
- }
-
- PendingIntent getLogPendingIntent() {
- // Let the configure Button show the Dashboard
- Intent intent = new Intent(getApplicationContext(),Dashboard.class);
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- PendingIntent startLW = PendingIntent.getActivity(getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
- intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
- return startLW;
-
- }
-
-
- private LocalServerSocket openManagmentInterface(int tries) {
- // Could take a while to open connection
- String socketname = (getCacheDir().getAbsolutePath() + "/" + "mgmtsocket");
- LocalSocket sock = new LocalSocket();
-
- while(tries > 0 && !sock.isConnected()) {
- try {
- sock.bind(new LocalSocketAddress(socketname,
- LocalSocketAddress.Namespace.FILESYSTEM));
- } catch (IOException e) {
- // wait 300 ms before retrying
- try { Thread.sleep(300);
- } catch (InterruptedException e1) {}
-
- }
- tries--;
- }
-
- try {
- LocalServerSocket lss = new LocalServerSocket(sock.getFileDescriptor());
- return lss;
- } catch (IOException e) {
- e.printStackTrace();
- }
- return null;
-
-
- }
-
- void registerNetworkStateReceiver() {
- // Registers BroadcastReceiver to track network connection changes.
- IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
- mNetworkStateReceiver = new NetworkSateReceiver(mSocketManager);
- this.registerReceiver(mNetworkStateReceiver, filter);
- }
-
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
-
- if( intent != null && intent.getAction() !=null &&
- (intent.getAction().equals(START_SERVICE) || intent.getAction().equals(RETRIEVE_SERVICE)) )
- return START_NOT_STICKY;
-
-
- // Extract information from the intent.
- String prefix = getPackageName();
- String[] argv = intent.getStringArrayExtra(prefix + ".ARGV");
- String nativelibdir = intent.getStringExtra(prefix + ".nativelib");
- String profileUUID = intent.getStringExtra(prefix + ".profileUUID");
-
- mProfile = ProfileManager.get(profileUUID);
-
- //showNotification("Starting VPN " + mProfile.mName,"Starting VPN " + mProfile.mName, false,0);
-
-
- OpenVPN.addStateListener(this);
-
- // Set a flag that we are starting a new VPN
- mStarting=true;
- // Stop the previous session by interrupting the thread.
- if(OpenVpnManagementThread.stopOpenVPN()){
- // an old was asked to exit, wait 2s
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- }
- }
-
- if (mProcessThread!=null) {
- mProcessThread.interrupt();
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- }
- }
- // An old running VPN should now be exited
- mStarting=false;
-
-
- // Open the Management Interface
- LocalServerSocket mgmtsocket = openManagmentInterface(8);
-
- if(mgmtsocket!=null) {
- // start a Thread that handles incoming messages of the managment socket
- mSocketManager = new OpenVpnManagementThread(mProfile,mgmtsocket,this);
- mSocketManagerThread = new Thread(mSocketManager,"OpenVPNMgmtThread");
- mSocketManagerThread.start();
- OpenVPN.logInfo("started Socket Thread");
- registerNetworkStateReceiver();
- }
-
-
- // Start a new session by creating a new thread.
- OpenVPNThread processThread = new OpenVPNThread(this, argv,nativelibdir);
-
- mProcessThread = new Thread(processThread, "OpenVPNProcessThread");
- mProcessThread.start();
-
- ProfileManager.setConnectedVpnProfile(this, mProfile);
-
- return START_NOT_STICKY;
- }
-
- @Override
- public void onDestroy() {
- if (mProcessThread != null) {
- mSocketManager.managmentCommand("signal SIGINT\n");
-
- mProcessThread.interrupt();
-
- }
- if (mNetworkStateReceiver!= null) {
- this.unregisterReceiver(mNetworkStateReceiver);
- }
-
- }
-
-
-
- public ParcelFileDescriptor openTun() {
- Builder builder = new Builder();
-
- if(mLocalIP==null && mLocalIPv6==null) {
- OpenVPN.logMessage(0, "", getString(R.string.opentun_no_ipaddr));
- return null;
- }
-
- if(mLocalIP!=null) {
- builder.addAddress(mLocalIP.mIp, mLocalIP.len);
- }
-
- if(mLocalIPv6!=null) {
- String[] ipv6parts = mLocalIPv6.split("/");
- builder.addAddress(ipv6parts[0],Integer.parseInt(ipv6parts[1]));
- }
-
-
- for (String dns : mDnslist ) {
- try {
- builder.addDnsServer(dns);
- } catch (IllegalArgumentException iae) {
- OpenVPN.logError(R.string.dns_add_error, dns,iae.getLocalizedMessage());
- }
- }
-
-
- builder.setMtu(mMtu);
-
-
- for (CIDRIP route:mRoutes) {
- try {
- builder.addRoute(route.mIp, route.len);
- } catch (IllegalArgumentException ia) {
- OpenVPN.logMessage(0, "", getString(R.string.route_rejected) + route + " " + ia.getLocalizedMessage());
- }
- }
-
- for(String v6route:mRoutesv6) {
- try {
- String[] v6parts = v6route.split("/");
- builder.addRoute(v6parts[0],Integer.parseInt(v6parts[1]));
- } catch (IllegalArgumentException ia) {
- OpenVPN.logMessage(0, "", getString(R.string.route_rejected) + v6route + " " + ia.getLocalizedMessage());
- }
- }
-
- if(mDomain!=null)
- builder.addSearchDomain(mDomain);
-
- String bconfig[] = new String[6];
-
- bconfig[0]= getString(R.string.last_openvpn_tun_config);
- bconfig[1] = getString(R.string.local_ip_info,mLocalIP.mIp,mLocalIP.len,mLocalIPv6, mMtu);
- bconfig[2] = getString(R.string.dns_server_info, joinString(mDnslist));
- bconfig[3] = getString(R.string.dns_domain_info, mDomain);
- bconfig[4] = getString(R.string.routes_info, joinString(mRoutes));
- bconfig[5] = getString(R.string.routes_info6, joinString(mRoutesv6));
-
- String session = mProfile.mLocation;
- /* we don't want the IP address in the notification bar
- if(mLocalIP!=null && mLocalIPv6!=null)
- session = getString(R.string.session_ipv6string,session, mLocalIP, mLocalIPv6);
- else if (mLocalIP !=null)
- session= getString(R.string.session_ipv4string, session, mLocalIP);
- */
- builder.setSession(session);
-
-
- OpenVPN.logBuilderConfig(bconfig);
-
- // No DNS Server, log a warning
- if(mDnslist.size()==0)
- OpenVPN.logInfo(R.string.warn_no_dns);
-
- // Reset information
- mDnslist.clear();
- mRoutes.clear();
- mRoutesv6.clear();
- mLocalIP=null;
- mLocalIPv6=null;
- mDomain=null;
-
- builder.setConfigureIntent(getLogPendingIntent());
-
- try {
- ParcelFileDescriptor pfd = builder.establish();
- return pfd;
- } catch (Exception e) {
- OpenVPN.logMessage(0, "", getString(R.string.tun_open_error));
- OpenVPN.logMessage(0, "", getString(R.string.error) + e.getLocalizedMessage());
- OpenVPN.logMessage(0, "", getString(R.string.tun_error_helpful));
- return null;
- }
-
- }
-
-
- // Ugly, but java has no such method
- private <T> String joinString(Vector<T> vec) {
- String ret = "";
- if(vec.size() > 0){
- ret = vec.get(0).toString();
- for(int i=1;i < vec.size();i++) {
- ret = ret + ", " + vec.get(i).toString();
- }
- }
- return ret;
- }
-
-
-
-
-
-
- public void addDNS(String dns) {
- mDnslist.add(dns);
- }
-
-
- public void setDomain(String domain) {
- if(mDomain==null) {
- mDomain=domain;
- }
- }
-
-
- public void addRoute(String dest, String mask) {
- CIDRIP route = new CIDRIP(dest, mask);
- if(route.len == 32 && !mask.equals("255.255.255.255")) {
- OpenVPN.logMessage(0, "", getString(R.string.route_not_cidr,dest,mask));
- }
-
- if(route.normalise())
- OpenVPN.logMessage(0, "", getString(R.string.route_not_netip,dest,route.len,route.mIp));
-
- mRoutes.add(route);
- }
-
- public void addRoutev6(String extra) {
- mRoutesv6.add(extra);
- }
-
-
- public void setLocalIP(String local, String netmask,int mtu, String mode) {
- mLocalIP = new CIDRIP(local, netmask);
- mMtu = mtu;
-
- if(mLocalIP.len == 32 && !netmask.equals("255.255.255.255")) {
- // get the netmask as IP
- long netint = CIDRIP.getInt(netmask);
- if(Math.abs(netint - mLocalIP.getInt()) ==1) {
- if(mode.equals("net30"))
- mLocalIP.len=30;
- else
- mLocalIP.len=31;
- } else {
- OpenVPN.logMessage(0, "", getString(R.string.ip_not_cidr, local,netmask,mode));
- }
- }
- }
-
- public void setLocalIPv6(String ipv6addr) {
- mLocalIPv6 = ipv6addr;
- }
-
- public boolean isRunning() {
- if (mStarting == true || mProcessThread != null)
- return true;
- else
- return false;
- }
-
- @Override
- public void updateState(String state,String logmessage, int resid) {
- // If the process is not running, ignore any state,
- // Notification should be invisible in this state
- android.util.Log.d("OpenVpnService", "updateState(" + state + ","+logmessage);
-
- if(mProcessThread==null) {
- if(mNotificationManager != null)
- mNotificationManager.cancel(OPENVPN_STATUS);
- return;
- }
- if("CONNECTED".equalsIgnoreCase(state)) {
- mNotificationManager.cancel(OPENVPN_STATUS);
- } else if(!"BYTECOUNT".equals(state)) {
-
- // Other notifications are shown,
- // This also mean we are no longer connected, ignore bytecount messages until next
- // CONNECTED
- String ticker = getString(resid);
- boolean persist = false;
- if (("NOPROCESS".equals(state) ) || ("EXITING").equals(state)){
- showNotification(state, getString(R.string.eip_state_not_connected), ticker, false, 0, persist);
- if(getSharedPreferences(Dashboard.SHARED_PREFERENCES, Activity.MODE_PRIVATE) != null)
- getSharedPreferences(Dashboard.SHARED_PREFERENCES, Activity.MODE_PRIVATE).edit().putBoolean(Dashboard.START_ON_BOOT, false).commit();
- }
- else if (state.equals("GET_CONFIG") || state.equals("ASSIGN_IP")){ //don't show them in the notification message
- }
- else{
- persist = true;
- showNotification(state, getString(resid) +" " + logmessage,ticker,false,0,persist);
- }
- }
- }
-
- @Override
- public boolean handleMessage(Message msg) {
- Runnable r = msg.getCallback();
- if(r!=null){
- r.run();
- return true;
- } else {
- return false;
- }
- }
-}
diff --git a/app/src/main/java/se/leap/openvpn/VpnProfile.java b/app/src/main/java/se/leap/openvpn/VpnProfile.java
deleted file mode 100644
index 481819ad..00000000
--- a/app/src/main/java/se/leap/openvpn/VpnProfile.java
+++ /dev/null
@@ -1,758 +0,0 @@
-package se.leap.openvpn;
-
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Serializable;
-import java.security.PrivateKey;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.util.Collection;
-import java.util.UUID;
-import java.util.Vector;
-
-import org.spongycastle.util.io.pem.PemObject;
-import org.spongycastle.util.io.pem.PemWriter;
-
-import se.leap.bitmaskclient.ConfigHelper;
-import se.leap.bitmaskclient.Dashboard;
-import se.leap.bitmaskclient.EIP;
-import se.leap.bitmaskclient.Provider;
-import se.leap.bitmaskclient.R;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.ApplicationInfo;
-import android.preference.PreferenceManager;
-import android.security.KeyChain;
-import android.security.KeyChainException;
-
-public class VpnProfile implements Serializable{
- // Parcable
- /**
- *
- */
- private static final long serialVersionUID = 7085688938959334563L;
- static final int TYPE_CERTIFICATES=0;
- static final int TYPE_PKCS12=1;
- static final int TYPE_KEYSTORE=2;
- public static final int TYPE_USERPASS = 3;
- public static final int TYPE_STATICKEYS = 4;
- public static final int TYPE_USERPASS_CERTIFICATES = 5;
- public static final int TYPE_USERPASS_PKCS12 = 6;
- public static final int TYPE_USERPASS_KEYSTORE = 7;
-
- // Don't change this, not all parts of the program use this constant
- public static final String EXTRA_PROFILEUUID = "se.leap.bitmaskclient.profileUUID"; // TODO this feels wrong. See Issue #1494
- public static final String INLINE_TAG = "[[INLINE]]";
- private static final String OVPNCONFIGFILE = "android.conf";
-
- protected transient String mTransientPW=null;
- protected transient String mTransientPCKS12PW=null;
- private transient PrivateKey mPrivateKey;
- protected boolean profileDleted=false;
-
-
- public static String DEFAULT_DNS1="131.234.137.23";
- public static String DEFAULT_DNS2="131.234.137.24";
-
- // Public attributes, since I got mad with getter/setter
- // set members to default values
- private UUID mUuid;
- public int mAuthenticationType = TYPE_CERTIFICATES ;
- public String mName;
- public String mLocation;
- public String mAlias;
- public String mClientCertFilename;
- public String mTLSAuthDirection="";
- public String mTLSAuthFilename;
- public String mClientKeyFilename;
- public String mCaFilename;
- public boolean mUseLzo=true;
- public String mServerPort= "1194" ;
- public boolean mUseUdp = true;
- public String mPKCS12Filename;
- public String mPKCS12Password;
- public boolean mUseTLSAuth = false;
- public String mServerName = "openvpn.blinkt.de" ;
- public String mDNS1=DEFAULT_DNS1;
- public String mDNS2=DEFAULT_DNS2;
- public String mIPv4Address;
- public String mIPv6Address;
- public boolean mOverrideDNS=false;
- public String mSearchDomain="blinkt.de";
- public boolean mUseDefaultRoute=true;
- public boolean mUsePull=true;
- public String mCustomRoutes;
- public boolean mCheckRemoteCN=false;
- public boolean mExpectTLSCert=true;
- public String mRemoteCN="";
- public String mPassword="";
- public String mUsername="";
- public boolean mRoutenopull=false;
- public boolean mUseRandomHostname=false;
- public boolean mUseFloat=false;
- public boolean mUseCustomConfig=false;
- public String mCustomConfigOptions="";
- public String mVerb="1";
- public String mCipher="";
- public boolean mNobind=false;
- public boolean mUseDefaultRoutev6=true;
- public String mCustomRoutesv6="";
- public String mKeyPassword="";
- public boolean mPersistTun = false;
- public String mConnectRetryMax="5";
- public String mConnectRetry="10";
- public boolean mUserEditable=true;
-
- static final String MINIVPN = "miniopenvpn";
-
-
-
-
-
-
- public void clearDefaults() {
- mServerName="unkown";
- mUsePull=false;
- mUseLzo=false;
- mUseDefaultRoute=false;
- mUseDefaultRoutev6=false;
- mExpectTLSCert=false;
- mPersistTun = false;
- }
-
-
- public static String openVpnEscape(String unescaped) {
- if(unescaped==null)
- return null;
- String escapedString = unescaped.replace("\\", "\\\\");
- escapedString = escapedString.replace("\"","\\\"");
- escapedString = escapedString.replace("\n","\\n");
-
- if (escapedString.equals(unescaped) && !escapedString.contains(" ") && !escapedString.contains("#"))
- return unescaped;
- else
- return '"' + escapedString + '"';
- }
-
-
- static final String OVPNCONFIGCA = "android-ca.pem";
- static final String OVPNCONFIGUSERCERT = "android-user.pem";
-
-
- public VpnProfile(String name) {
- mUuid = UUID.randomUUID();
- mName = name;
- }
-
- public UUID getUUID() {
- return mUuid;
-
- }
-
- public String getName() {
- return mName;
- }
-
-
- public String getConfigFile(Context context)
- {
-
- File cacheDir= context.getCacheDir();
- String cfg="";
-
- // Enable managment interface
- cfg += "# Enables connection to GUI\n";
- cfg += "management ";
-
- cfg +=cacheDir.getAbsolutePath() + "/" + "mgmtsocket";
- cfg += " unix\n";
- cfg += "management-client\n";
- // Not needed, see updated man page in 2.3
- //cfg += "management-signal\n";
- cfg += "management-query-passwords\n";
- cfg += "management-hold\n\n";
-
- /* tmp-dir patched out :)
- cfg+="# /tmp does not exist on Android\n";
- cfg+="tmp-dir ";
- cfg+=cacheDir.getAbsolutePath();
- cfg+="\n\n"; */
-
- cfg+="# Log window is better readable this way\n";
- cfg+="suppress-timestamps\n";
-
-
-
- boolean useTLSClient = (mAuthenticationType != TYPE_STATICKEYS);
-
- if(useTLSClient && mUsePull)
- cfg+="client\n";
- else if (mUsePull)
- cfg+="pull\n";
- else if(useTLSClient)
- cfg+="tls-client\n";
-
-
- cfg+="verb " + mVerb + "\n";
-
- if(mConnectRetryMax ==null) {
- mConnectRetryMax="5";
- }
-
- if(!mConnectRetryMax.equals("-1"))
- cfg+="connect-retry-max " + mConnectRetryMax+ "\n";
-
- if(mConnectRetry==null)
- mConnectRetry="10";
-
-
- cfg+="connect-retry " + mConnectRetry + "\n";
-
- cfg+="resolv-retry 60\n";
-
-
-
- // We cannot use anything else than tun
- cfg+="dev tun\n";
-
- // Server Address
- cfg+="remote ";
- cfg+=mServerName;
- cfg+=" ";
- cfg+=mServerPort;
- if(mUseUdp)
- cfg+=" udp\n";
- else
- cfg+=" tcp-client\n";
-
-
-
-
- switch(mAuthenticationType) {
- case VpnProfile.TYPE_USERPASS_CERTIFICATES:
- cfg+="auth-user-pass\n";
- case VpnProfile.TYPE_CERTIFICATES:
- /*// Ca
- cfg+=insertFileData("ca",mCaFilename);
-
- // Client Cert + Key
- cfg+=insertFileData("key",mClientKeyFilename);
- cfg+=insertFileData("cert",mClientCertFilename);
-*/
- // FIXME This is all we need...The whole switch statement can go...
- SharedPreferences preferences = context.getSharedPreferences(Dashboard.SHARED_PREFERENCES, context.MODE_PRIVATE);
- cfg+="<ca>\n"+preferences.getString(Provider.CA_CERT, "")+"\n</ca>\n";
- cfg+="<key>\n"+preferences.getString(EIP.PRIVATE_KEY, "")+"\n</key>\n";
- cfg+="<cert>\n"+preferences.getString(EIP.CERTIFICATE, "")+"\n</cert>\n";
-
- break;
- case VpnProfile.TYPE_USERPASS_PKCS12:
- cfg+="auth-user-pass\n";
- case VpnProfile.TYPE_PKCS12:
- cfg+=insertFileData("pkcs12",mPKCS12Filename);
- break;
-
- case VpnProfile.TYPE_USERPASS_KEYSTORE:
- cfg+="auth-user-pass\n";
- case VpnProfile.TYPE_KEYSTORE:
- cfg+="ca " + cacheDir.getAbsolutePath() + "/" + OVPNCONFIGCA + "\n";
- cfg+="cert " + cacheDir.getAbsolutePath() + "/" + OVPNCONFIGUSERCERT + "\n";
- cfg+="management-external-key\n";
-
- break;
- case VpnProfile.TYPE_USERPASS:
- cfg+="auth-user-pass\n";
- cfg+=insertFileData("ca",mCaFilename);
- }
-
- if(mUseLzo) {
- cfg+="comp-lzo\n";
- }
-
- if(mUseTLSAuth) {
- if(mAuthenticationType==TYPE_STATICKEYS)
- cfg+=insertFileData("secret",mTLSAuthFilename);
- else
- cfg+=insertFileData("tls-auth",mTLSAuthFilename);
-
- if(nonNull(mTLSAuthDirection)) {
- cfg+= "key-direction ";
- cfg+= mTLSAuthDirection;
- cfg+="\n";
- }
-
- }
-
- if(!mUsePull ) {
- if(nonNull(mIPv4Address))
- cfg +="ifconfig " + cidrToIPAndNetmask(mIPv4Address) + "\n";
-
- if(nonNull(mIPv6Address))
- cfg +="ifconfig-ipv6 " + mIPv6Address + "\n";
- }
-
- if(mUsePull && mRoutenopull)
- cfg += "route-nopull\n";
-
- String routes = "";
- int numroutes=0;
- if(mUseDefaultRoute)
- routes += "route 0.0.0.0 0.0.0.0\n";
- else
- for(String route:getCustomRoutes()) {
- routes += "route " + route + "\n";
- numroutes++;
- }
-
-
- if(mUseDefaultRoutev6)
- cfg += "route-ipv6 ::/0\n";
- else
- for(String route:getCustomRoutesv6()) {
- routes += "route-ipv6 " + route + "\n";
- numroutes++;
- }
-
- // Round number to next 100
- if(numroutes> 90) {
- numroutes = ((numroutes / 100)+1) * 100;
- cfg+="# Alot of routes are set, increase max-routes\n";
- cfg+="max-routes " + numroutes + "\n";
- }
- cfg+=routes;
-
- if(mOverrideDNS || !mUsePull) {
- if(nonNull(mDNS1))
- cfg+="dhcp-option DNS " + mDNS1 + "\n";
- if(nonNull(mDNS2))
- cfg+="dhcp-option DNS " + mDNS2 + "\n";
- if(nonNull(mSearchDomain))
- cfg+="dhcp-option DOMAIN " + mSearchDomain + "\n";
-
- }
-
- if(mNobind)
- cfg+="nobind\n";
-
-
-
- // Authentication
- if(mCheckRemoteCN) {
- if(mRemoteCN == null || mRemoteCN.equals("") )
- cfg+="tls-remote " + mServerName + "\n";
- else
- cfg += "tls-remote " + openVpnEscape(mRemoteCN) + "\n";
- }
- if(mExpectTLSCert)
- cfg += "remote-cert-tls server\n";
-
-
- if(nonNull(mCipher)){
- cfg += "cipher " + mCipher + "\n";
- }
-
-
- // Obscure Settings dialog
- if(mUseRandomHostname)
- cfg += "#my favorite options :)\nremote-random-hostname\n";
-
- if(mUseFloat)
- cfg+= "float\n";
-
- if(mPersistTun) {
- cfg+= "persist-tun\n";
- cfg+= "# persist-tun also sets persist-remote-ip to avoid DNS resolve problem\n";
- cfg+= "persist-remote-ip\n";
- }
-
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
- boolean usesystemproxy = prefs.getBoolean("usesystemproxy", true);
- if(usesystemproxy) {
- cfg+= "# Use system proxy setting\n";
- cfg+= "management-query-proxy\n";
- }
-
-
- if(mUseCustomConfig) {
- cfg += "# Custom configuration options\n";
- cfg += "# You are on your on own here :)\n";
- cfg += mCustomConfigOptions;
- cfg += "\n";
-
- }
-
-
-
- return cfg;
- }
-
- //! Put inline data inline and other data as normal escaped filename
- private String insertFileData(String cfgentry, String filedata) {
- if(filedata==null) {
- return String.format("%s %s\n",cfgentry,"missing");
- }else if(filedata.startsWith(VpnProfile.INLINE_TAG)){
- String datawoheader = filedata.substring(VpnProfile.INLINE_TAG.length());
- return String.format("<%s>\n%s\n</%s>\n",cfgentry,datawoheader,cfgentry);
- } else {
- return String.format("%s %s\n",cfgentry,openVpnEscape(filedata));
- }
- }
-
- private boolean nonNull(String val) {
- if(val == null || val.equals(""))
- return false;
- else
- return true;
- }
-
- private Collection<String> getCustomRoutes() {
- Vector<String> cidrRoutes=new Vector<String>();
- if(mCustomRoutes==null) {
- // No routes set, return empty vector
- return cidrRoutes;
- }
- for(String route:mCustomRoutes.split("[\n \t]")) {
- if(!route.equals("")) {
- String cidrroute = cidrToIPAndNetmask(route);
- if(cidrRoutes == null)
- return null;
-
- cidrRoutes.add(cidrroute);
- }
- }
-
- return cidrRoutes;
- }
-
- private Collection<String> getCustomRoutesv6() {
- Vector<String> cidrRoutes=new Vector<String>();
- if(mCustomRoutesv6==null) {
- // No routes set, return empty vector
- return cidrRoutes;
- }
- for(String route:mCustomRoutesv6.split("[\n \t]")) {
- if(!route.equals("")) {
- cidrRoutes.add(route);
- }
- }
-
- return cidrRoutes;
- }
-
-
-
- private String cidrToIPAndNetmask(String route) {
- String[] parts = route.split("/");
-
- // No /xx, assume /32 as netmask
- if (parts.length ==1)
- parts = (route + "/32").split("/");
-
- if (parts.length!=2)
- return null;
- int len;
- try {
- len = Integer.parseInt(parts[1]);
- } catch(NumberFormatException ne) {
- return null;
- }
- if (len <0 || len >32)
- return null;
-
-
- long nm = 0xffffffffl;
- nm = (nm << (32-len)) & 0xffffffffl;
-
- String netmask =String.format("%d.%d.%d.%d", (nm & 0xff000000) >> 24,(nm & 0xff0000) >> 16, (nm & 0xff00) >> 8 ,nm & 0xff );
- return parts[0] + " " + netmask;
- }
-
-
-
- private String[] buildOpenvpnArgv(File cacheDir)
- {
- Vector<String> args = new Vector<String>();
-
- // Add fixed paramenters
- //args.add("/data/data/se.leap.openvpn/lib/openvpn");
- args.add(cacheDir.getAbsolutePath() +"/" + VpnProfile.MINIVPN);
-
- args.add("--config");
- args.add(cacheDir.getAbsolutePath() + "/" + OVPNCONFIGFILE);
- // Silences script security warning
-
- args.add("script-security");
- args.add("0");
-
-
- return (String[]) args.toArray(new String[args.size()]);
- }
-
- public Intent prepareIntent(Context context) {
- String prefix = context.getPackageName();
-
- Intent intent = new Intent(context,OpenVpnService.class);
-
- if(mAuthenticationType == VpnProfile.TYPE_KEYSTORE || mAuthenticationType == VpnProfile.TYPE_USERPASS_KEYSTORE) {
- /*if(!saveCertificates(context))
- return null;*/
- }
-
- intent.putExtra(prefix + ".ARGV" , buildOpenvpnArgv(context.getCacheDir()));
- intent.putExtra(prefix + ".profileUUID", mUuid.toString());
-
- ApplicationInfo info = context.getApplicationInfo();
- intent.putExtra(prefix +".nativelib",info.nativeLibraryDir);
-
- try {
- FileWriter cfg = new FileWriter(context.getCacheDir().getAbsolutePath() + "/" + OVPNCONFIGFILE);
- cfg.write(getConfigFile(context));
- cfg.flush();
- cfg.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
-
- return intent;
- }
-
- private boolean saveCertificates(Context context) {
- PrivateKey privateKey = null;
- X509Certificate[] cachain=null;
- try {
- privateKey = KeyChain.getPrivateKey(context,mAlias);
- mPrivateKey = privateKey;
-
- cachain = KeyChain.getCertificateChain(context, mAlias);
- if(cachain.length <= 1 && !nonNull(mCaFilename))
- OpenVPN.logMessage(0, "", context.getString(R.string.keychain_nocacert));
-
- for(X509Certificate cert:cachain) {
- OpenVPN.logInfo(R.string.cert_from_keystore,cert.getSubjectDN());
- }
-
-
-
-
- if(nonNull(mCaFilename)) {
- try {
- Certificate cacert = getCacertFromFile();
- X509Certificate[] newcachain = new X509Certificate[cachain.length+1];
- for(int i=0;i<cachain.length;i++)
- newcachain[i]=cachain[i];
-
- newcachain[cachain.length-1]=(X509Certificate) cacert;
-
- } catch (Exception e) {
- OpenVPN.logError("Could not read CA certificate" + e.getLocalizedMessage());
- }
- }
-
-
- FileWriter fout = new FileWriter(context.getCacheDir().getAbsolutePath() + "/" + VpnProfile.OVPNCONFIGCA);
- PemWriter pw = new PemWriter(fout);
- for(X509Certificate cert:cachain) {
- pw.writeObject(new PemObject("CERTIFICATE", cert.getEncoded()));
- }
-
- pw.close();
-
-
- if(cachain.length>= 1){
- X509Certificate usercert = cachain[0];
-
- FileWriter userout = new FileWriter(context.getCacheDir().getAbsolutePath() + "/" + VpnProfile.OVPNCONFIGUSERCERT);
-
- PemWriter upw = new PemWriter(userout);
- upw.writeObject(new PemObject("CERTIFICATE", usercert.getEncoded()));
- upw.close();
-
- }
-
- return true;
- } catch (InterruptedException e) {
- e.printStackTrace();
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (CertificateException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } catch (KeyChainException e) {
- OpenVPN.logMessage(0,"",context.getString(R.string.keychain_access));
- }
- return false;
- }
- private Certificate getCacertFromFile() throws FileNotFoundException, CertificateException {
- CertificateFactory certFact = CertificateFactory.getInstance("X.509");
-
- InputStream inStream;
-
- if(mCaFilename.startsWith(INLINE_TAG))
- inStream = new ByteArrayInputStream(mCaFilename.replace(INLINE_TAG,"").getBytes());
- else
- inStream = new FileInputStream(mCaFilename);
-
- return certFact.generateCertificate(inStream);
- }
-
-
- //! Return an error if somethign is wrong
- public int checkProfile(Context context) {
-/* if(mAuthenticationType==TYPE_KEYSTORE || mAuthenticationType==TYPE_USERPASS_KEYSTORE) {
- if(mAlias==null)
- return R.string.no_keystore_cert_selected;
- }*/
-
- if(!mUsePull) {
- if(mIPv4Address == null || cidrToIPAndNetmask(mIPv4Address) == null)
- return R.string.ipv4_format_error;
- }
- if(isUserPWAuth() && !nonNull(mUsername)) {
- return R.string.error_empty_username;
- }
- if(!mUseDefaultRoute && getCustomRoutes()==null)
- return R.string.custom_route_format_error;
-
- // Everything okay
- return R.string.no_error_found;
-
- }
-
- //! Openvpn asks for a "Private Key", this should be pkcs12 key
- //
- public String getPasswordPrivateKey() {
- if(mTransientPCKS12PW!=null) {
- String pwcopy = mTransientPCKS12PW;
- mTransientPCKS12PW=null;
- return pwcopy;
- }
- switch (mAuthenticationType) {
- case TYPE_PKCS12:
- case TYPE_USERPASS_PKCS12:
- return mPKCS12Password;
-
- case TYPE_CERTIFICATES:
- case TYPE_USERPASS_CERTIFICATES:
- return mKeyPassword;
-
- case TYPE_USERPASS:
- case TYPE_STATICKEYS:
- default:
- return null;
- }
- }
- private boolean isUserPWAuth() {
- switch(mAuthenticationType) {
- case TYPE_USERPASS:
- case TYPE_USERPASS_CERTIFICATES:
- case TYPE_USERPASS_KEYSTORE:
- case TYPE_USERPASS_PKCS12:
- return true;
- default:
- return false;
-
- }
- }
-
-
- public boolean requireTLSKeyPassword() {
- if(!nonNull(mClientKeyFilename))
- return false;
-
- String data = "";
- if(mClientKeyFilename.startsWith(INLINE_TAG))
- data = mClientKeyFilename;
- else {
- char[] buf = new char[2048];
- FileReader fr;
- try {
- fr = new FileReader(mClientKeyFilename);
- int len = fr.read(buf);
- while(len > 0 ) {
- data += new String(buf,0,len);
- len = fr.read(buf);
- }
- fr.close();
- } catch (FileNotFoundException e) {
- return false;
- } catch (IOException e) {
- return false;
- }
-
- }
-
- if(data.contains("Proc-Type: 4,ENCRYPTED"))
- return true;
- else if(data.contains("-----BEGIN ENCRYPTED PRIVATE KEY-----"))
- return true;
- else
- return false;
- }
-
- public int needUserPWInput() {
- if((mAuthenticationType == TYPE_PKCS12 || mAuthenticationType == TYPE_USERPASS_PKCS12)&&
- (mPKCS12Password == null || mPKCS12Password.equals(""))) {
- if(mTransientPCKS12PW==null)
- return R.string.pkcs12_file_encryption_key;
- }
-
- if(mAuthenticationType == TYPE_CERTIFICATES || mAuthenticationType == TYPE_USERPASS_CERTIFICATES) {
- if(requireTLSKeyPassword() && !nonNull(mKeyPassword))
- if(mTransientPCKS12PW==null) {
- return R.string.private_key_password;
- }
- }
-
- if(isUserPWAuth() && (mPassword.equals("") || mPassword == null)) {
- if(mTransientPW==null)
- return R.string.password;
-
- }
- return 0;
- }
-
- public String getPasswordAuth() {
- if(mTransientPW!=null) {
- String pwcopy = mTransientPW;
- mTransientPW=null;
- return pwcopy;
- } else {
- return mPassword;
- }
- }
-
-
- // Used by the Array Adapter
- @Override
- public String toString() {
- return mName;
- }
-
-
- public String getUUIDString() {
- return mUuid.toString();
- }
-
-
- public PrivateKey getKeystoreKey() {
- return mPrivateKey;
- }
-
-
-
-}
-
-
-
-