diff options
author | Parménides GV <parmegv@sdf.org> | 2014-06-13 12:13:04 +0200 |
---|---|---|
committer | Parménides GV <parmegv@sdf.org> | 2014-06-13 12:13:04 +0200 |
commit | 3a71bc9e4aa4296f460e2e3c55de74c9852477ad (patch) | |
tree | f816597a7c4322137f0657e7aa2bf392404d1870 /app/src/main/java | |
parent | cfe67bfd8260253ce9288225b9e26f666d27133f (diff) | |
parent | 36247e71df88fa13c6c5a887de3b11d9a883615f (diff) |
Merge branch 'feature/establish-an-upstream-relationship-with-ics-openvpn-codebase-#5381' into develop
Diffstat (limited to 'app/src/main/java')
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; - } - - - -} - - - - |