diff options
Diffstat (limited to 'app/src/main/java')
30 files changed, 1072 insertions, 670 deletions
diff --git a/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java b/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java index d7f3e110..02abd7a1 100644 --- a/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java +++ b/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2012-2014 Arne Schwabe - * Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn; diff --git a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java index fb2ba90d..4f747d21 100644 --- a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java +++ b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2012-2014 Arne Schwabe - * Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn; @@ -42,6 +42,7 @@ import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Collection; +import java.util.HashSet; import java.util.Locale; import java.util.UUID; import java.util.Vector; @@ -51,13 +52,14 @@ import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; +import de.blinkt.openvpn.core.Connection; import de.blinkt.openvpn.core.NativeUtils; import de.blinkt.openvpn.core.OpenVPNService; import de.blinkt.openvpn.core.VPNLaunchHelper; import de.blinkt.openvpn.core.VpnStatus; import de.blinkt.openvpn.core.X509Utils; -public class VpnProfile implements Serializable { +public class VpnProfile implements Serializable, Cloneable { // 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 @@ -71,7 +73,7 @@ public class VpnProfile implements Serializable { private static final long serialVersionUID = 7085688938959334563L; public static final int MAXLOGLEVEL = 4; - public static final int CURRENT_PROFILE_VERSION = 2; + public static final int CURRENT_PROFILE_VERSION = 5; public static final int DEFAULT_MSSFIX_SIZE = 1450; public static String DEFAULT_DNS1 = "8.8.8.8"; public static String DEFAULT_DNS2 = "8.8.4.4"; @@ -106,12 +108,10 @@ public class VpnProfile implements Serializable { 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; @@ -152,6 +152,16 @@ public class VpnProfile implements Serializable { public String mExcludedRoutes; public String mExcludedRoutesv6; public int mMssFix =0; // -1 is default, + public Connection[] mConnections = new Connection[0]; + public boolean mRemoteRandom=false; + public HashSet<String> mAllowedAppsVpn = new HashSet<String>(); + public boolean mAllowedAppsVpnAreDisallowed = true; + + + /* Options no long used in new profiles */ + public String mServerName = "openvpn.blinkt.de"; + public String mServerPort = "1194"; + public boolean mUseUdp = true; @@ -159,6 +169,9 @@ public class VpnProfile implements Serializable { mUuid = UUID.randomUUID(); mName = name; mProfileVersion = CURRENT_PROFILE_VERSION; + + mConnections = new Connection[1]; + mConnections[0] = new Connection(); } public static String openVpnEscape(String unescaped) { @@ -206,7 +219,30 @@ public class VpnProfile implements Serializable { mAllowLocalLAN = Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT; } + if (mProfileVersion < 4) { + moveOptionsToConnection(); + mAllowedAppsVpnAreDisallowed=true; + } + if (mAllowedAppsVpn==null) + mAllowedAppsVpn = new HashSet<String>(); + if (mConnections ==null) + mConnections = new Connection[0]; + mProfileVersion= CURRENT_PROFILE_VERSION; + + } + + private void moveOptionsToConnection() { + mConnections = new Connection[1]; + Connection conn = new Connection(); + + conn.mServerName = mServerName; + conn.mServerPort = mServerPort; + conn.mUseUdp = mUseUdp; + conn.mCustomConfiguration = ""; + + mConnections[0] = conn; + } public String getConfigFile(Context context, boolean configForOvpn3) { @@ -267,15 +303,27 @@ public class VpnProfile implements Serializable { // 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"; + + boolean canUsePlainRemotes = true; + + if (mConnections.length==1) { + cfg += mConnections[0].getConnectionBlock(); + } else { + for (Connection conn : mConnections) { + canUsePlainRemotes = canUsePlainRemotes && conn.isOnlyRemote(); + } + + if (mRemoteRandom) + cfg+="remote-random\n"; + + if (canUsePlainRemotes) { + for (Connection conn : mConnections) { + if (conn.mEnabled) { + cfg += conn.getConnectionBlock(); + } + } + } + } switch (mAuthenticationType) { @@ -365,11 +413,6 @@ public class VpnProfile implements Serializable { } } - 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"; @@ -405,7 +448,7 @@ public class VpnProfile implements Serializable { if (mAuthenticationType != TYPE_STATICKEYS) { if (mCheckRemoteCN) { if (mRemoteCN == null || mRemoteCN.equals("")) - cfg += "verify-x509-name " + mServerName + " name\n"; + cfg += "verify-x509-name " + mConnections[0].mServerName + " name\n"; else switch (mX509AuthType) { @@ -470,6 +513,19 @@ public class VpnProfile implements Serializable { } + if (!canUsePlainRemotes) { + cfg += "# Connection Options are at the end to allow global options (and global custom options) to influence connection blocks\n"; + for (Connection conn : mConnections) { + if (conn.mEnabled) { + cfg += "<connection>\n"; + cfg += conn.getConnectionBlock(); + cfg += "</connection>\n"; + } + } + } + + + return cfg; } @@ -639,6 +695,27 @@ public class VpnProfile implements Serializable { } } + @Override + protected VpnProfile clone() throws CloneNotSupportedException { + VpnProfile copy = (VpnProfile) super.clone(); + copy.mUuid = UUID.randomUUID(); + copy.mConnections = mConnections.clone(); + copy.mAllowedAppsVpn = (HashSet<String>) mAllowedAppsVpn.clone(); + return copy; + } + + public VpnProfile copy(String name) { + try { + VpnProfile copy = (VpnProfile) clone(); + copy.mName = name; + return copy; + + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + return null; + } + } + class NoCertReturnedException extends Exception { public NoCertReturnedException (String msg) { @@ -769,6 +846,14 @@ public class VpnProfile implements Serializable { if (!mUseDefaultRoute && (getCustomRoutes(mCustomRoutes) == null || getCustomRoutes(mExcludedRoutes) ==null)) return R.string.custom_route_format_error; + boolean noRemoteEnabled = true; + for (Connection c : mConnections) + if (c.mEnabled) + noRemoteEnabled = false; + + if(noRemoteEnabled) + return R.string.remote_no_server_selected; + // Everything okay return R.string.no_error_found; diff --git a/app/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java b/app/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java index 4940d5d6..dfd815e4 100644 --- a/app/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java +++ b/app/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2012-2014 Arne Schwabe - * Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.activities; diff --git a/app/src/main/java/de/blinkt/openvpn/activities/LogWindow.java b/app/src/main/java/de/blinkt/openvpn/activities/LogWindow.java index 5e4f9517..45f09c8e 100644 --- a/app/src/main/java/de/blinkt/openvpn/activities/LogWindow.java +++ b/app/src/main/java/de/blinkt/openvpn/activities/LogWindow.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2012-2014 Arne Schwabe - * Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.activities; diff --git a/app/src/main/java/de/blinkt/openvpn/core/CIDRIP.java b/app/src/main/java/de/blinkt/openvpn/core/CIDRIP.java index ac9a8ccb..e525abd5 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/CIDRIP.java +++ b/app/src/main/java/de/blinkt/openvpn/core/CIDRIP.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2012-2014 Arne Schwabe - * Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; diff --git a/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java b/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java index 0d8230b7..5dc96bbc 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java +++ b/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java @@ -1,13 +1,17 @@ /* * Copyright (c) 2012-2014 Arne Schwabe - * Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; +import android.text.TextUtils; +import android.util.Pair; + import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; +import java.io.StringReader; import java.util.Collections; import java.util.HashMap; import java.util.Locale; @@ -28,48 +32,49 @@ public class ConfigParser { private HashMap<String, Vector<Vector<String>>> options = new HashMap<String, Vector<Vector<String>>>(); private HashMap<String, Vector<String>> meta = new HashMap<String, Vector<String>>(); - - private boolean extraRemotesAsCustom=false; - public void parseConfig(Reader reader) throws IOException, ConfigParseError { - BufferedReader br =new BufferedReader(reader); + BufferedReader br = new BufferedReader(reader); - int lineno=0; - while (true){ - String line = br.readLine(); - lineno++; - if(line==null) - break; + int lineno = 0; + try { + while (true) { + String line = br.readLine(); + lineno++; + if (line == null) + break; - if (lineno==1 && (line.startsWith("PK\003\004") - || (line.startsWith("PK\007\008")))) + if (lineno == 1 && (line.startsWith("PK\003\004") + || (line.startsWith("PK\007\008")))) throw new ConfigParseError("Input looks like a ZIP Archive. Import is only possible for OpenVPN config files (.ovpn/.conf)"); - // 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); + // 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; + if (args.size() == 0) + continue; - if(args.get(0).startsWith("--")) - args.set(0, args.get(0).substring(2)); + if (args.get(0).startsWith("--")) + args.set(0, args.get(0).substring(2)); - checkinlinefile(args,br); + checkinlinefile(args, br); - String optionname = args.get(0); - if(!options.containsKey(optionname)) { - options.put(optionname, new Vector<Vector<String>>()); - } - options.get(optionname).add(args); - } + String optionname = args.get(0); + if (!options.containsKey(optionname)) { + options.put(optionname, new Vector<Vector<String>>()); + } + options.get(optionname).add(args); + } + } catch (java.lang.OutOfMemoryError memoryError) { + throw new ConfigParseError("File too large to parse: " + memoryError.getLocalizedMessage()); + } } private Vector<String> parsemeta(String line) { @@ -98,7 +103,7 @@ public class ConfigParser { break; else { inlinefile+=line; - inlinefile+= "\n"; + inlinefile+= "\n"; } } while(true); @@ -132,7 +137,7 @@ public class ConfigParser { // adapted openvpn's parse function to java private Vector<String> parseline(String line) throws ConfigParseError { - Vector<String> parameters = new Vector<String>(); + Vector<String> parameters = new Vector<String>(); if (line.length()==0) return parameters; @@ -145,12 +150,12 @@ public class ConfigParser { int pos=0; String currentarg=""; - do { + do { // Emulate the c parsing ... char in; if(pos < line.length()) in = line.charAt(pos); - else + else in = '\0'; if (!backslash && in == '\\' && state != linestate.readin_single_quote) @@ -228,10 +233,7 @@ public class ConfigParser { } - final String[] unsupportedOptions = { "config", - "connection", - "proto-force", - "remote-random", + final String[] unsupportedOptions = { "config", "tls-server" }; @@ -299,7 +301,7 @@ public class ConfigParser { "remote", "float", "port", -// "connect-retry", + "connect-retry", "connect-timeout", "connect-retry-max", "link-mtu", @@ -325,7 +327,7 @@ public class ConfigParser { // This method is far too long @SuppressWarnings("ConstantConditions") - public VpnProfile convertProfile() throws ConfigParseError{ + public VpnProfile convertProfile() throws ConfigParseError, IOException { boolean noauthtypeset=true; VpnProfile np = new VpnProfile(CONVERTED_PROFILE); // Pull, client, tls-client @@ -338,7 +340,7 @@ public class ConfigParser { } Vector<String> secret = getOption("secret", 1, 2); - if(secret!=null) + if(secret!=null) { np.mAuthenticationType=VpnProfile.TYPE_STATICKEYS; noauthtypeset=false; @@ -362,7 +364,7 @@ public class ConfigParser { if (route.size() >= 4) gateway = route.get(3); - String net = route.get(1); + String net = route.get(1); try { CIDRIP cidr = new CIDRIP(net, netmask); if (gateway.equals("net_gateway")) @@ -398,7 +400,7 @@ public class ConfigParser { Vector<Vector<String>> tlsauthoptions = getAllOption("tls-auth", 1, 2); if(tlsauthoptions!=null) { for(Vector<String> tlsauth:tlsauthoptions) { - if(tlsauth!=null) + if(tlsauth!=null) { if(!tlsauth.get(1).equals("[inline]")) { np.mTLSAuthFilename=tlsauth.get(1); @@ -458,36 +460,6 @@ public class ConfigParser { 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> rport = getOption("rport", 1,1); - if(rport!=null){ - np.mServerPort = rport.get(1); - } - - Vector<String> proto = getOption("proto", 1,1); - if(proto!=null){ - np.mUseUdp=isUdpProto(proto.get(1)); - } - - // Parse remote config - 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)); - case 3: - np.mServerPort = remote.get(2); - case 2: - np.mServerName = remote.get(1); - } - } - Vector<Vector<String>> dhcpoptions = getAllOption("dhcp-option", 2, 2); @@ -581,18 +553,18 @@ public class ConfigParser { if(verifyx509name!=null){ np.mRemoteCN = verifyx509name.get(1); np.mCheckRemoteCN=true; - if(verifyx509name.size()>2) { + 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 + 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); @@ -615,7 +587,7 @@ public class ConfigParser { if(connectretrymax!=null) np.mConnectRetryMax =connectretrymax.get(1); - Vector<Vector<String>> remotetls = getAllOption("remote-cert-tls", 1, 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; @@ -632,14 +604,55 @@ public class ConfigParser { np.mAuthenticationType=VpnProfile.TYPE_USERPASS_KEYSTORE; } if(authuser.size()>1) { - // Set option value to password get to get cance to embed later. + // Set option value to password get to embed later. np.mUsername=null; - np.mPassword=authuser.get(1); - useEmbbedUserAuth(np,authuser.get(1)); + useEmbbedUserAuth(np, authuser.get(1)); } } - // Parse OpenVPN Access Server extra + Pair<Connection, Connection[]> conns = parseConnectionOptions(null); + np.mConnections =conns.second; + + Vector<Vector<String>> connectionBlocks = getAllOption("connection", 1, 1); + + if (np.mConnections.length > 0 && connectionBlocks !=null ) { + throw new ConfigParseError("Using a <connection> block and --remote is not allowed."); + } + + if (connectionBlocks!=null) { + np.mConnections = new Connection[connectionBlocks.size()]; + + int connIndex = 0; + for (Vector<String> conn : connectionBlocks) { + Pair<Connection, Connection[]> connectionBlockConnection = + parseConnection(conn.get(1), conns.first); + + if (connectionBlockConnection.second.length != 1) + throw new ConfigParseError("A <connection> block must have exactly one remote"); + np.mConnections[connIndex] = connectionBlockConnection.second[0]; + connIndex++; + } + } + if(getOption("remote-random", 0, 0) != null) + np.mRemoteRandom=true; + + Vector<String> protoforce = getOption("proto-force", 1, 1); + if(protoforce!=null) { + boolean disableUDP; + String protoToDisable = protoforce.get(1); + if (protoToDisable.equals("udp")) + disableUDP=true; + else if (protoToDisable.equals("tcp")) + disableUDP=false; + else + throw new ConfigParseError(String.format("Unknown protocol %s in proto-force", protoToDisable)); + + for (Connection conn:np.mConnections) + if(conn.mUseUdp==disableUDP) + conn.mEnabled=false; + } + + // Parse OpenVPN Access Server extra Vector<String> friendlyname = meta.get("FRIENDLY_NAME"); if(friendlyname !=null && friendlyname.size() > 1) np.mName=friendlyname.get(1); @@ -649,20 +662,95 @@ public class ConfigParser { 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); + checkIgnoreAndInvalidOptions(np); fixup(np); return np; } + private Pair<Connection, Connection[]> parseConnection(String connection, Connection defaultValues) throws IOException, ConfigParseError { + // Parse a connection Block as a new configuration file + + + ConfigParser connectionParser = new ConfigParser(); + StringReader reader = new StringReader(connection.substring(VpnProfile.INLINE_TAG.length())); + connectionParser.parseConfig(reader); + + Pair<Connection, Connection[]> conn = connectionParser.parseConnectionOptions(defaultValues); + + return conn; + } + + private Pair<Connection, Connection[]> parseConnectionOptions(Connection connDefault) throws ConfigParseError { + Connection conn; + if (connDefault!=null) + try { + conn = connDefault.clone(); + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + return null; + } + else + conn = new Connection(); + + Vector<String> port = getOption("port", 1,1); + if(port!=null){ + conn.mServerPort = port.get(1); + } + + Vector<String> rport = getOption("rport", 1,1); + if(rport!=null){ + conn.mServerPort = rport.get(1); + } + + Vector<String> proto = getOption("proto", 1,1); + if(proto!=null){ + conn.mUseUdp=isUdpProto(proto.get(1)); + } + + + // Parse remote config + Vector<Vector<String>> remotes = getAllOption("remote",1,3); + + + // Assume that we need custom options if connectionDefault are set + if(connDefault!=null) { + for (Vector<Vector<String>> option : options.values()) { + + conn.mCustomConfiguration += getOptionStrings(option); + + } + if (!TextUtils.isEmpty(conn.mCustomConfiguration)) + conn.mUseCustomConfig = true; + } + // Make remotes empty to simplify code + if (remotes==null) + remotes = new Vector<Vector<String>>(); + + Connection[] connections = new Connection[remotes.size()]; + + + int i=0; + for (Vector<String> remote: remotes) { + try { + connections[i] = conn.clone(); + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + } + switch (remote.size()) { + case 4: + connections[i].mUseUdp=isUdpProto(remote.get(3)); + case 3: + connections[i].mServerPort = remote.get(2); + case 2: + connections[i].mServerName = remote.get(1); + } + i++; + } + return Pair.create(conn, connections); + + } + private void checkRedirectParameters(VpnProfile np, Vector<Vector<String>> defgw) { for (Vector<String> redirect: defgw) for (int i=1;i<redirect.size();i++){ @@ -673,25 +761,21 @@ public class ConfigParser { } } - public void useExtraRemotesAsCustom(boolean b) { - this.extraRemotesAsCustom = b; - } - private boolean isUdpProto(String proto) throws ConfigParseError { boolean isudp; if(proto.equals("udp") || proto.equals("udp6")) isudp=true; else if (proto.equals("tcp-client") || - proto.equals("tcp") || + proto.equals("tcp") || proto.equals("tcp6") || proto.endsWith("tcp6-client")) isudp =false; - else + else throw new ConfigParseError("Unsupported option to --proto " + proto); return isudp; } - static public void useEmbbedUserAuth(VpnProfile np,String inlinedata) + static public void useEmbbedUserAuth(VpnProfile np, String inlinedata) { String data = VpnProfile.getEmbeddedContent(inlinedata); String[] parts = data.split("\n"); diff --git a/app/src/main/java/de/blinkt/openvpn/core/Connection.java b/app/src/main/java/de/blinkt/openvpn/core/Connection.java new file mode 100644 index 00000000..b10664ce --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/core/Connection.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2012-2014 Arne Schwabe + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt + */ + +package de.blinkt.openvpn.core; + +import android.text.TextUtils; + +import java.io.Serializable; + +public class Connection implements Serializable, Cloneable { + public String mServerName = "openvpn.blinkt.de"; + public String mServerPort = "1194"; + public boolean mUseUdp = true; + public String mCustomConfiguration=""; + public boolean mUseCustomConfig=false; + public boolean mEnabled=true; + + private static final long serialVersionUID = 92031902903829089L; + + + public String getConnectionBlock() { + String cfg=""; + + // Server Address + cfg += "remote "; + cfg += mServerName; + cfg += " "; + cfg += mServerPort; + if (mUseUdp) + cfg += " udp\n"; + else + cfg += " tcp-client\n"; + + if (!TextUtils.isEmpty(mCustomConfiguration) && mUseCustomConfig) { + cfg += mCustomConfiguration; + cfg += "\n"; + } + return cfg; + } + + @Override + public Connection clone() throws CloneNotSupportedException { + return (Connection) super.clone(); + } + + public boolean isOnlyRemote() { + return TextUtils.isEmpty(mCustomConfiguration) || !mUseCustomConfig; + } +} diff --git a/app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java b/app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java index 0d75ae51..4ccf5472 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java +++ b/app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2012-2014 Arne Schwabe - * Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; @@ -182,18 +182,14 @@ public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountL screen = connectState.DISCONNECTED; if (shouldBeConnected()) { - if (sendusr1) { - if (lastNetwork == -1) { - mManagement.resume(); - } else { - mManagement.reconnect(); - } + if (lastNetwork == -1) { + mManagement.resume(); } else { mManagement.networkChange(); + } } - lastNetwork = newnet; } } else if (networkInfo == null) { diff --git a/app/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java b/app/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java index 83e760ca..56a574dc 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java +++ b/app/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2012-2014 Arne Schwabe - * Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; diff --git a/app/src/main/java/de/blinkt/openvpn/core/LollipopDeviceStateListener.java b/app/src/main/java/de/blinkt/openvpn/core/LollipopDeviceStateListener.java new file mode 100644 index 00000000..440458e4 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/core/LollipopDeviceStateListener.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2012-2014 Arne Schwabe + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt + */ + +package de.blinkt.openvpn.core; + +import android.annotation.TargetApi; +import android.net.ConnectivityManager; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.os.Build; + +/** + * Created by arne on 26.11.14. + */ +@TargetApi(Build.VERSION_CODES.LOLLIPOP) +public class LollipopDeviceStateListener extends ConnectivityManager.NetworkCallback { + + private String mLastConnectedStatus; + private String mLastLinkProperties; + private String mLastNetworkCapabilities; + + @Override + public void onAvailable(Network network) { + super.onAvailable(network); + + if (!network.toString().equals(mLastConnectedStatus)) { + mLastConnectedStatus = network.toString(); + VpnStatus.logDebug("Connected to " + mLastConnectedStatus); + } + } + + @Override + public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) { + super.onLinkPropertiesChanged(network, linkProperties); + + if (!linkProperties.toString().equals(mLastLinkProperties)) { + mLastLinkProperties = linkProperties.toString(); + VpnStatus.logDebug(String.format("Linkproperties of %s: %s", network, linkProperties)); + } + } + + @Override + public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) { + super.onCapabilitiesChanged(network, networkCapabilities); + if (!networkCapabilities.toString().equals(mLastNetworkCapabilities)) { + mLastNetworkCapabilities = networkCapabilities.toString(); + VpnStatus.logDebug(String.format("Network capabilities of %s: %s", network, networkCapabilities)); + } + } +} diff --git a/app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java b/app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java index 6d7ffdf2..f67b7730 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java +++ b/app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2012-2014 Arne Schwabe - * Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; @@ -9,6 +9,7 @@ import java.security.InvalidKeyException; public class NativeUtils { public static native byte[] rsasign(byte[] input,int pkey) throws InvalidKeyException; + public static native String[] getIfconfig() throws IllegalArgumentException; static native void jniclose(int fdint); static { diff --git a/app/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java b/app/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java index 35f46513..26354689 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java +++ b/app/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2012-2014 Arne Schwabe - * Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; @@ -21,6 +21,8 @@ import se.leap.bitmaskclient.BuildConfig; public class NetworkSpace { + + static class ipAddress implements Comparable<ipAddress> { private BigInteger netAddress; public int networkMask; @@ -198,6 +200,13 @@ public class NetworkSpace { mIpAddresses.add(new ipAddress(cidrIp, include)); } + public void addIPSplit(CIDRIP cidrIp, boolean include) { + ipAddress newIP = new ipAddress(cidrIp, include); + ipAddress[] splitIps = newIP.split(); + for (ipAddress split: splitIps) + mIpAddresses.add(split); + } + void addIPv6(Inet6Address address, int mask, boolean included) { mIpAddresses.add(new ipAddress(address, mask, included)); } diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java index e90c16d1..1f28c77d 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2012-2014 Arne Schwabe - * Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java index d9830955..578d95e7 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2012-2014 Arne Schwabe - * Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; @@ -14,7 +14,9 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.net.ConnectivityManager; +import android.net.NetworkRequest; import android.net.VpnService; import android.os.Binder; import android.os.Build; @@ -23,6 +25,7 @@ import android.os.IBinder; import android.os.Message; import android.os.ParcelFileDescriptor; import android.preference.PreferenceManager; +import android.system.OsConstants; import android.text.TextUtils; import android.util.Log; @@ -81,6 +84,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac private String mLastTunCfg; private String mRemoteGW; private final Object mProcessLock = new Object(); + private LollipopDeviceStateListener mLollipopDeviceStateListener; // 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) { @@ -266,6 +270,9 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac mDeviceStateReceiver = new DeviceStateReceiver(magnagement); registerReceiver(mDeviceStateReceiver, filter); VpnStatus.addByteCountListener(mDeviceStateReceiver); + + /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + addLollipopCMListener(); */ } synchronized void unregisterDeviceStateReceiver() { @@ -280,6 +287,10 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac iae.printStackTrace(); } mDeviceStateReceiver = null; + + /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + removeLollipopCMListener();*/ + } public void userPause(boolean shouldBePaused) { @@ -320,7 +331,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac mProfile = ProfileManager.getLastConnectedProfile(this, false); /* Got no profile, just stop */ - if (mProfile==null) { + if (mProfile == null) { Log.d("OpenVPN", "Got no last connected profile on null intent. Stopping"); stopSelf(startId); return START_NOT_STICKY; @@ -431,7 +442,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac private OpenVPNManagement instantiateOpenVPN3Core() { try { Class cl = Class.forName("de.blinkt.openvpn.core.OpenVPNThreadv3"); - return (OpenVPNManagement) cl.getConstructor(OpenVPNService.class,VpnProfile.class).newInstance(this,mProfile); + return (OpenVPNManagement) cl.getConstructor(OpenVPNService.class, VpnProfile.class).newInstance(this, mProfile); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InstantiationException e) { @@ -474,6 +485,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac 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); @@ -490,6 +502,10 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac VpnStatus.logInfo(R.string.last_openvpn_tun_config); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && mProfile.mAllowLocalLAN) + { + allowAllAFFamilies(builder); + } if (mLocalIP == null && mLocalIPv6 == null) { VpnStatus.logError(getString(R.string.opentun_no_ipaddr)); @@ -497,6 +513,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } if (mLocalIP != null) { + addLocalNetworksToRoutes(); try { builder.addAddress(mLocalIP.mIp, mLocalIP.len); } catch (IllegalArgumentException iae) { @@ -527,7 +544,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac String release = Build.VERSION.RELEASE; if ((Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT && !release.startsWith("4.4.3") - && !release.startsWith("4.4.4") && !release.startsWith("4.4.5") && !release.startsWith("4.4.6")) + && !release.startsWith("4.4.4") && !release.startsWith("4.4.5") && !release.startsWith("4.4.6")) && mMtu < 1280) { VpnStatus.logInfo(String.format(Locale.US, "Forcing MTU to 1280 instead of %d to workaround Android Bug #70916", mMtu)); builder.setMtu(1280); @@ -560,8 +577,12 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac 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.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)); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + setAllowedVpnPackages(builder); + } + String session = mProfile.mName; if (mLocalIP != null && mLocalIPv6 != null) @@ -601,6 +622,82 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + private void allowAllAFFamilies(Builder builder) { + builder.allowFamily(OsConstants.AF_INET); + builder.allowFamily(OsConstants.AF_INET6); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + void removeLollipopCMListener() { + ConnectivityManager cm = (ConnectivityManager) getBaseContext().getSystemService(CONNECTIVITY_SERVICE); + cm.unregisterNetworkCallback(mLollipopDeviceStateListener); + mLollipopDeviceStateListener = null; + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + void addLollipopCMListener() { + ConnectivityManager cm = (ConnectivityManager) getBaseContext().getSystemService(CONNECTIVITY_SERVICE); + NetworkRequest.Builder nrb = new NetworkRequest.Builder(); + + mLollipopDeviceStateListener = new LollipopDeviceStateListener(); + cm.registerNetworkCallback(nrb.build(), mLollipopDeviceStateListener); + } + + private void addLocalNetworksToRoutes() { + + // Add local network interfaces + String[] localRoutes = NativeUtils.getIfconfig(); + + // The format of mLocalRoutes is kind of broken because I don't really like JNI + for (int i = 0; i < localRoutes.length; i += 3) { + String intf = localRoutes[i]; + String ipAddr = localRoutes[i + 1]; + String netMask = localRoutes[i + 2]; + + if (intf == null || intf.equals("lo") || + intf.startsWith("tun") || intf.startsWith("rmnet")) + continue; + + if (ipAddr==null || netMask == null) { + VpnStatus.logError("Local routes are broken?! (Report to author) " + TextUtils.join("|", localRoutes)); + continue; + } + + if (ipAddr.equals(mLocalIP.mIp)) + continue; + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT && !mProfile.mAllowLocalLAN) { + mRoutes.addIPSplit(new CIDRIP(ipAddr, netMask), true); + + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && mProfile.mAllowLocalLAN) + mRoutes.addIP(new CIDRIP(ipAddr, netMask), false); + } + } + + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + private void setAllowedVpnPackages(Builder builder) { + for (String pkg : mProfile.mAllowedAppsVpn) { + try { + if (mProfile.mAllowedAppsVpnAreDisallowed) { + builder.addDisallowedApplication(pkg); + } else { + builder.addAllowedApplication(pkg); + } + } catch (PackageManager.NameNotFoundException e) { + mProfile.mAllowedAppsVpn.remove(pkg); + VpnStatus.logInfo(R.string.app_no_longer_exists, pkg); + } + } + + if (mProfile.mAllowedAppsVpnAreDisallowed) { + VpnStatus.logDebug(R.string.disallowed_vpn_apps_info, TextUtils.join(", ", mProfile.mAllowedAppsVpn)); + } else { + VpnStatus.logDebug(R.string.allowed_vpn_apps_info, TextUtils.join(", ", mProfile.mAllowedAppsVpn)); + } + } + public void addDNS(String dns) { mDnslist.add(dns); } @@ -611,28 +708,30 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } } - /** Route that is always included, used by the v3 core */ - public void addRoute (CIDRIP route) { + /** + * 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) { + 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); + NetworkSpace.ipAddress gatewayIP = new NetworkSpace.ipAddress(new CIDRIP(gateway, 32), false); - if (mLocalIP==null) { + 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); + NetworkSpace.ipAddress localNet = new NetworkSpace.ipAddress(mLocalIP, true); if (localNet.containsNet(gatewayIP)) - include=true; + include = true; - if (gateway!= null && + if (gateway != null && (gateway.equals("255.255.255.255") || gateway.equals(mRemoteGW))) - include=true; + include = true; if (route.len == 32 && !mask.equals("255.255.255.255")) { @@ -664,7 +763,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } private boolean isAndroidTunDevice(String device) { - return device!=null && + return device != null && (device.startsWith("tun") || "(null)".equals(device) || "vpnservice-tun".equals(device)); } @@ -679,7 +778,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac public void setLocalIP(String local, String netmask, int mtu, String mode) { mLocalIP = new CIDRIP(local, netmask); mMtu = mtu; - mRemoteGW=null; + mRemoteGW = null; long netMaskAsInt = CIDRIP.getInt(netmask); @@ -687,14 +786,17 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac // get the netmask as IP int masklen; - if ("net30".equals(mode)) + long mask; + if ("net30".equals(mode)) { masklen = 30; - else + mask = 0xfffffffc; + } else { masklen = 31; + mask = 0xfffffffe; + } - int mask = ~( 1 << (32 - (mLocalIP.len +1))); // Netmask is Ip address +/-1, assume net30/p2p with small net - if ((netMaskAsInt & mask) == (mLocalIP.getInt() & mask )) { + if ((netMaskAsInt & mask) == (mLocalIP.getInt() & mask)) { mLocalIP.len = masklen; } else { mLocalIP.len = 32; @@ -702,13 +804,18 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac VpnStatus.logWarning(R.string.ip_not_cidr, local, netmask, mode); } } - if (("p2p".equals(mode) && mLocalIP.len < 32) || ("net30".equals(mode) && mLocalIP.len < 30)) { + if (("p2p".equals(mode) && mLocalIP.len < 32) || ("net30".equals(mode) && mLocalIP.len < 30)) { VpnStatus.logWarning(R.string.ip_looks_like_subnet, local, netmask, mode); } + /* Workaround for Lollipop, it does not route traffic to the VPNs own network mask */ + if (mLocalIP.len <= 31 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + addRoute(mLocalIP); + + // Configurations are sometimes really broken... - mRemoteGW=netmask; + mRemoteGW = netmask; } public void setLocalIPv6(String ipv6addr) { @@ -810,7 +917,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } else { String release = Build.VERSION.RELEASE; if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT && !release.startsWith("4.4.3") - && !release.startsWith("4.4.4") && !release.startsWith("4.4.5") && !release.startsWith("4.4.6")) + && !release.startsWith("4.4.4") && !release.startsWith("4.4.5") && !release.startsWith("4.4.6")) // There will be probably no 4.4.4 or 4.4.5 version, so don't waste effort to do parsing here return "OPEN_AFTER_CLOSE"; else diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java index e36a5b8a..298a6c40 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2012-2014 Arne Schwabe - * Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java index 37094a1b..1c3b3362 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2012-2014 Arne Schwabe - * Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; @@ -157,7 +157,7 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { } } catch (IOException e) { - if (!e.getMessage().equals("socket closed")) + if (!e.getMessage().equals("socket closed") && !e.getMessage().equals("Connection reset by peer")) VpnStatus.logException(e); } synchronized (active) { diff --git a/app/src/main/java/de/blinkt/openvpn/core/PRNGFixes.java b/app/src/main/java/de/blinkt/openvpn/core/PRNGFixes.java index bca0a4ab..a788426a 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/PRNGFixes.java +++ b/app/src/main/java/de/blinkt/openvpn/core/PRNGFixes.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2012-2014 Arne Schwabe - * Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core;/* diff --git a/app/src/main/java/de/blinkt/openvpn/core/ProfileManager.java b/app/src/main/java/de/blinkt/openvpn/core/ProfileManager.java index 2a26152e..1ebc0a57 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/ProfileManager.java +++ b/app/src/main/java/de/blinkt/openvpn/core/ProfileManager.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2012-2014 Arne Schwabe - * Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; diff --git a/app/src/main/java/de/blinkt/openvpn/core/ProxyDetection.java b/app/src/main/java/de/blinkt/openvpn/core/ProxyDetection.java index cf953863..6e2abb13 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/ProxyDetection.java +++ b/app/src/main/java/de/blinkt/openvpn/core/ProxyDetection.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2012-2014 Arne Schwabe - * Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; diff --git a/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java b/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java index 208aa359..73ed05bc 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java +++ b/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2012-2014 Arne Schwabe - * Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; @@ -76,7 +76,6 @@ public class VPNLaunchHelper { args.add("--config"); args.add(c.getCacheDir().getAbsolutePath() + "/" + OVPNCONFIGFILE); - return args.toArray(new String[args.size()]); } diff --git a/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java b/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java index 25558f13..ffc8097d 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java +++ b/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2012-2014 Arne Schwabe - * Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; @@ -480,7 +480,11 @@ public class VpnStatus { newLogItem(new LogItem(LogLevel.INFO, message)); } - public static void logInfo(int resourceId, Object... args) { + public static void logDebug(String message) { + newLogItem(new LogItem(LogLevel.DEBUG, message)); + } + + public static void logInfo(int resourceId, Object... args) { newLogItem(new LogItem(LogLevel.INFO, resourceId, args)); } diff --git a/app/src/main/java/de/blinkt/openvpn/core/X509Utils.java b/app/src/main/java/de/blinkt/openvpn/core/X509Utils.java index ff383e0f..0786967b 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/X509Utils.java +++ b/app/src/main/java/de/blinkt/openvpn/core/X509Utils.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2012-2014 Arne Schwabe - * Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; diff --git a/app/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java b/app/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java index 77fc21e6..199caa63 100644 --- a/app/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java +++ b/app/src/main/java/de/blinkt/openvpn/fragments/LogFragment.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2012-2014 Arne Schwabe - * Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.fragments; diff --git a/app/src/main/java/de/blinkt/openvpn/views/SeekBarTicks.java b/app/src/main/java/de/blinkt/openvpn/views/SeekBarTicks.java index e25c2859..82378b00 100644 --- a/app/src/main/java/de/blinkt/openvpn/views/SeekBarTicks.java +++ b/app/src/main/java/de/blinkt/openvpn/views/SeekBarTicks.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2012-2014 Arne Schwabe - * Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.views; diff --git a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java index 52b22695..4e6120ab 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java +++ b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java @@ -17,37 +17,21 @@ package se.leap.bitmaskclient; import android.annotation.SuppressLint; -import android.app.Activity; -import android.app.AlertDialog; -import android.app.DialogFragment; -import android.app.FragmentTransaction; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.SharedPreferences; +import android.app.*; +import android.content.*; import android.content.pm.PackageManager.NameNotFoundException; -import android.os.Bundle; -import android.os.Handler; -import android.os.ResultReceiver; +import android.os.*; import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.widget.ProgressBar; -import android.widget.TextView; +import android.view.*; +import android.widget.*; import org.jetbrains.annotations.NotNull; -import org.json.JSONException; -import org.json.JSONObject; +import org.json.*; +import java.net.*; -import java.net.MalformedURLException; -import java.net.URL; - -import butterknife.ButterKnife; -import butterknife.InjectView; -import de.blinkt.openvpn.activities.LogWindow; -import se.leap.bitmaskclient.eip.Constants; -import se.leap.bitmaskclient.eip.EIP; -import se.leap.bitmaskclient.eip.EipStatus; +import butterknife.*; +import de.blinkt.openvpn.activities.*; +import se.leap.bitmaskclient.eip.*; /** * The main user facing Activity of Bitmask Android, consisting of status, controls, @@ -58,16 +42,16 @@ import se.leap.bitmaskclient.eip.EipStatus; */ public class Dashboard extends Activity implements SessionDialog.SessionDialogInterface, ProviderAPIResultReceiver.Receiver { - protected static final int CONFIGURE_LEAP = 0; - protected static final int SWITCH_PROVIDER = 1; + protected static final int CONFIGURE_LEAP = 0; + protected static final int SWITCH_PROVIDER = 1; - final public static String TAG = Dashboard.class.getSimpleName(); - final public static String SHARED_PREFERENCES = "LEAPPreferences"; - final public static String ACTION_QUIT = "quit"; + public static final String TAG = Dashboard.class.getSimpleName(); + public static final String SHARED_PREFERENCES = "LEAPPreferences"; + public static final String ACTION_QUIT = "quit"; public static final String REQUEST_CODE = "request_code"; public static final String PARAMETERS = "dashboard parameters"; public static final String START_ON_BOOT = "dashboard start on boot"; - final public static String ON_BOOT = "dashboard on boot"; + public static final String ON_BOOT = "dashboard on boot"; public static final String APP_VERSION = "bitmask version"; private static Context app; @@ -81,6 +65,26 @@ public class Dashboard extends Activity implements SessionDialog.SessionDialogIn private Provider provider; private static boolean authed_eip; public ProviderAPIResultReceiver providerAPI_result_receiver; + private boolean switching_provider; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + app = this; + + PRNGFixes.apply(); + + preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); + fragment_manager = new FragmentManagerEnhanced(getFragmentManager()); + handleVersion(); + + provider = getSavedProvider(savedInstanceState); + if (provider == null || provider.getName().isEmpty()) + startActivityForResult(new Intent(this,ConfigurationWizard.class),CONFIGURE_LEAP); + else + buildDashboard(getIntent().getBooleanExtra(ON_BOOT, false)); + } @Override protected void onSaveInstanceState(@NotNull Bundle outState) { @@ -89,25 +93,6 @@ public class Dashboard extends Activity implements SessionDialog.SessionDialogIn super.onSaveInstanceState(outState); } - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - app = this; - - PRNGFixes.apply(); - - preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); - fragment_manager = new FragmentManagerEnhanced(getFragmentManager()); - handleVersion(); - - provider = getSavedProvider(savedInstanceState); - if (provider == null || provider.getName().isEmpty()) - startActivityForResult(new Intent(this,ConfigurationWizard.class),CONFIGURE_LEAP); - else - buildDashboard(getIntent().getBooleanExtra(ON_BOOT, false)); - } - private Provider getSavedProvider(Bundle savedInstanceState) { Provider provider = null; if(savedInstanceState != null) @@ -123,120 +108,109 @@ public class Dashboard extends Activity implements SessionDialog.SessionDialogIn try { provider = new Provider(new URL(preferences.getString(Provider.MAIN_URL, ""))); provider.define(new JSONObject(preferences.getString(Provider.KEY, ""))); - } catch (MalformedURLException | JSONException e) { + } catch (MalformedURLException e) { + e.printStackTrace(); + } catch (JSONException e) { e.printStackTrace(); } return provider; } - private void handleVersion() { try { int versionCode = getPackageManager().getPackageInfo(getPackageName(), 0).versionCode; int lastDetectedVersion = preferences.getInt(APP_VERSION, 0); preferences.edit().putInt(APP_VERSION, versionCode).apply(); - Log.d("Dashboard", "detected version code: " + versionCode); - Log.d("Dashboard", "last detected version code: " + lastDetectedVersion); switch(versionCode) { case 91: // 0.6.0 without Bug #5999 case 101: // 0.8.0 - if(!preferences.getString(Constants.KEY, "").isEmpty()) { - Intent rebuildVpnProfiles = new Intent(getApplicationContext(), EIP.class); - rebuildVpnProfiles.setAction(Constants.ACTION_UPDATE_EIP_SERVICE); - startService(rebuildVpnProfiles); - } + if(!preferences.getString(Constants.KEY, "").isEmpty()) + updateEipService(); break; } } catch (NameNotFoundException e) { - Log.d(TAG, "Handle version didn't find any " + getPackageName() + " package"); + Log.d(TAG, "Handle version didn't find any " + getPackageName() + " package"); } } @SuppressLint("CommitPrefEdits") @Override protected void onActivityResult(int requestCode, int resultCode, Intent data){ - Log.d(TAG, "onActivityResult: requestCode = " + requestCode); if ( requestCode == CONFIGURE_LEAP || requestCode == SWITCH_PROVIDER) { if ( resultCode == RESULT_OK ) { - preferences.edit().putBoolean(Constants.AUTHED_EIP, authed_eip).apply(); - updateEipService(); - - if (data.hasExtra(Provider.KEY)) { - provider = data.getParcelableExtra(Provider.KEY); - preferences.edit().putBoolean(Constants.PROVIDER_CONFIGURED, true).commit(); - preferences.edit().putString(Provider.MAIN_URL, provider.mainUrl().toString()).apply(); - preferences.edit().putString(Provider.KEY, provider.definition().toString()).apply(); - } - buildDashboard(false); - invalidateOptionsMenu(); - if (data.hasExtra(SessionDialog.TAG)) { - logInDialog(Bundle.EMPTY); - } - } else if (resultCode == RESULT_CANCELED && data.hasExtra(ACTION_QUIT)) { + preferences.edit().putBoolean(Constants.AUTHED_EIP, authed_eip).apply(); + updateEipService(); + + if (data.hasExtra(Provider.KEY)) { + provider = data.getParcelableExtra(Provider.KEY); + preferences.edit().putBoolean(Constants.PROVIDER_CONFIGURED, true).commit(); + preferences.edit().putString(Provider.MAIN_URL, provider.mainUrl().toString()).apply(); + preferences.edit().putString(Provider.KEY, provider.definition().toString()).apply(); + } + buildDashboard(false); + invalidateOptionsMenu(); + if (data.hasExtra(SessionDialog.TAG)) { + sessionDialog(Bundle.EMPTY); + } + } else if (resultCode == RESULT_CANCELED && data.hasExtra(ACTION_QUIT)) { finish(); - } else + } else configErrorDialog(); } else if(requestCode == EIP.DISCONNECT) { EipStatus.getInstance().setConnectedOrDisconnected(); } } - /** - * Dialog shown when encountering a configuration error. Such errors require - * reconfiguring LEAP or aborting the application. - */ - private void configErrorDialog() { - AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getAppContext()); - alertBuilder.setTitle(getResources().getString(R.string.setup_error_title)); - alertBuilder - .setMessage(getResources().getString(R.string.setup_error_text)) - .setCancelable(false) - .setPositiveButton(getResources().getString(R.string.setup_error_configure_button), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - startActivityForResult(new Intent(getAppContext(),ConfigurationWizard.class),CONFIGURE_LEAP); - } - }) - .setNegativeButton(getResources().getString(R.string.setup_error_close_button), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - preferences.edit().remove(Provider.KEY).remove(Constants.PROVIDER_CONFIGURED).apply(); - finish(); - } - }) - .show(); - } + private void configErrorDialog() { + AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getAppContext()); + alertBuilder.setTitle(getResources().getString(R.string.setup_error_title)); + alertBuilder + .setMessage(getResources().getString(R.string.setup_error_text)) + .setCancelable(false) + .setPositiveButton(getResources().getString(R.string.setup_error_configure_button), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + startActivityForResult(new Intent(getAppContext(),ConfigurationWizard.class),CONFIGURE_LEAP); + } + }) + .setNegativeButton(getResources().getString(R.string.setup_error_close_button), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + preferences.edit().remove(Provider.KEY).remove(Constants.PROVIDER_CONFIGURED).apply(); + finish(); + } + }) + .show(); + } - /** - * Inflates permanent UI elements of the View and contains logic for what - * service dependent UI elements to include. - */ - private void buildDashboard(boolean hide_and_turn_on_eip) { - setContentView(R.layout.dashboard); + /** + * Inflates permanent UI elements of the View and contains logic for what + * service dependent UI elements to include. + */ + private void buildDashboard(boolean hide_and_turn_on_eip) { + setContentView(R.layout.dashboard); ButterKnife.inject(this); - provider_name.setText(provider.getDomain()); - if ( provider.hasEIP()){ - + provider_name.setText(provider.getDomain()); + if ( provider.hasEIP()){ fragment_manager.removePreviousFragment(EipFragment.TAG); eip_fragment = new EipFragment(); - if (hide_and_turn_on_eip) { - preferences.edit().remove(Dashboard.START_ON_BOOT).apply(); - Bundle arguments = new Bundle(); - arguments.putBoolean(EipFragment.START_ON_BOOT, true); + if (hide_and_turn_on_eip) { + preferences.edit().remove(Dashboard.START_ON_BOOT).apply(); + Bundle arguments = new Bundle(); + arguments.putBoolean(EipFragment.START_ON_BOOT, true); if(eip_fragment != null) eip_fragment.setArguments(arguments); - } + } fragment_manager.replace(R.id.servicesCollection, eip_fragment, EipFragment.TAG); - - if (hide_and_turn_on_eip) { - onBackPressed(); - } - } + if (hide_and_turn_on_eip) { + onBackPressed(); + } } + } @Override public boolean onPrepareOptionsMenu(Menu menu) { @@ -267,114 +241,104 @@ public class Dashboard extends Activity implements SessionDialog.SessionDialogIn } return true; } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.client_dashboard, menu); + return true; + } - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.client_dashboard, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item){ - Intent intent; - switch (item.getItemId()){ - case R.id.about_leap: - 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.hasEIP()){ - if (preferences.getBoolean(Constants.AUTHED_EIP, false)) { - logOut(); - } - eip_fragment.askToStopEIP(); - } - preferences.edit().clear().apply(); - startActivityForResult(new Intent(this,ConfigurationWizard.class), SWITCH_PROVIDER); - return true; - case R.id.login_button: - logInDialog(Bundle.EMPTY); - return true; - case R.id.logout_button: - logOut(); - return true; - case R.id.signup_button: - signUpDialog(Bundle.EMPTY); - return true; - default: - return super.onOptionsItemSelected(item); - } - + @Override + public boolean onOptionsItemSelected(MenuItem item){ + Intent intent; + switch (item.getItemId()){ + case R.id.about_leap: + 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: + switching_provider = true; + if (preferences.getBoolean(Constants.AUTHED_EIP, false)) logOut(); + else switchProvider(); + return true; + case R.id.login_button: + sessionDialog(Bundle.EMPTY); + return true; + case R.id.logout_button: + logOut(); + return true; + case R.id.signup_button: + sessionDialog(Bundle.EMPTY); + return true; + default: + return super.onOptionsItemSelected(item); } - - protected Intent prepareProviderAPICommand() { - providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler()); - providerAPI_result_receiver.setReceiver(this); - - Intent command = new Intent(this, ProviderAPI.class); - - command.putExtra(ProviderAPI.RECEIVER_KEY, providerAPI_result_receiver); - return command; } - - /** - * Shows the log in dialog. - */ - public void logInDialog(Bundle resultData) { - FragmentTransaction transaction = fragment_manager.removePreviousFragment(SessionDialog.TAG); - DialogFragment newFragment = SessionDialog.newInstance(); - if(resultData != null && !resultData.isEmpty() && fragment_manager.findFragmentByTag(SessionDialog.TAG) == null) - newFragment.setArguments(resultData); - newFragment.show(transaction, SessionDialog.TAG); + @Override + public void signUp(String username, String password) { + Bundle parameters = bundleParameters(username, password); + providerApiCommand(parameters, R.string.signingup_message, ProviderAPI.SRP_REGISTER); } @Override public void logIn(String username, String password) { - Intent provider_API_command = prepareProviderAPICommand(); - Bundle parameters = provider_API_command.getExtras().getBundle(ProviderAPI.PARAMETERS); - if(parameters == null) - parameters = new Bundle(); - - parameters.putString(SessionDialog.USERNAME, username); - parameters.putString(SessionDialog.PASSWORD, password); + Bundle parameters = bundleParameters(username, password); + providerApiCommand(parameters, R.string.authenticating_message, ProviderAPI.SRP_AUTH); + } + + public void logOut() { + providerApiCommand(Bundle.EMPTY, R.string.logout_message, ProviderAPI.LOG_OUT); + } + private void downloadAuthedUserCertificate() { + Bundle parameters = new Bundle(); + parameters.putString(ConfigurationWizard.TYPE_OF_CERTIFICATE, ConfigurationWizard.AUTHED_CERTIFICATE); + + providerApiCommand(parameters, R.string.downloading_certificate_message, ProviderAPI.DOWNLOAD_CERTIFICATE); + } + + private Bundle bundleParameters(String username, String password) { + Bundle parameters = new Bundle(); + if(!username.isEmpty() && !password.isEmpty()) { + parameters.putString(SessionDialog.USERNAME, username); + parameters.putString(SessionDialog.PASSWORD, password); + } + return parameters; + } + + private void providerApiCommand(Bundle parameters, int progressbar_message_resId, String providerApi_action) { if(eip_fragment != null) { eip_fragment.progress_bar.setVisibility(ProgressBar.VISIBLE); - eip_fragment.status_message.setText(R.string.authenticating_message); + setStatusMessage(progressbar_message_resId); } - provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); - provider_API_command.setAction(ProviderAPI.SRP_AUTH); - startService(provider_API_command); + + Intent command = prepareProviderAPICommand(parameters, providerApi_action); + startService(command); } - public void cancelLoginOrSignup() { - EipStatus.getInstance().setConnectedOrDisconnected(); - } + protected Intent prepareProviderAPICommand(Bundle parameters, String action) { + providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler()); + providerAPI_result_receiver.setReceiver(this); - /** - * Asks ProviderAPI to log out. - */ - public void logOut() { - Intent provider_API_command = prepareProviderAPICommand(); - if(eip_fragment != null) { + Intent command = new Intent(this, ProviderAPI.class); + + command.putExtra(ProviderAPI.PARAMETERS, parameters); + command.putExtra(ProviderAPI.RECEIVER_KEY, providerAPI_result_receiver); + command.setAction(action); + return command; + } - eip_fragment.progress_bar.setVisibility(ProgressBar.VISIBLE); - eip_fragment.status_message.setText(R.string.logout_message); - } - provider_API_command.setAction(ProviderAPI.LOG_OUT); - startService(provider_API_command); + public void cancelLoginOrSignup() { + EipStatus.getInstance().setConnectedOrDisconnected(); } - /** - * Shows the sign up dialog. - */ - public void signUpDialog(Bundle resultData) { + public void sessionDialog(Bundle resultData) { FragmentTransaction transaction = fragment_manager.removePreviousFragment(SessionDialog.TAG); DialogFragment newFragment = SessionDialog.newInstance(); @@ -384,160 +348,118 @@ public class Dashboard extends Activity implements SessionDialog.SessionDialogIn newFragment.show(transaction, SessionDialog.TAG); } - @Override - public void signUp(String username, String password) { - Intent provider_API_command = prepareProviderAPICommand(); - Bundle parameters = provider_API_command.getExtras().getBundle(ProviderAPI.PARAMETERS); - if(parameters == null) - parameters = new Bundle(); - - parameters.putString(SessionDialog.USERNAME, username); - parameters.putString(SessionDialog.PASSWORD, password); - if(eip_fragment != null) { - eip_fragment.progress_bar.setVisibility(ProgressBar.VISIBLE); - eip_fragment.status_message.setText(R.string.signingup_message); - } - provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); - provider_API_command.setAction(ProviderAPI.SRP_REGISTER); - startService(provider_API_command); + private void switchProvider() { + if (provider.hasEIP()) eip_fragment.askToStopEIP(); + + preferences.edit().clear().apply(); + switching_provider = false; + startActivityForResult(new Intent(this, ConfigurationWizard.class), SWITCH_PROVIDER); } - /** - * Asks ProviderAPI to download an authenticated OpenVPN certificate. - */ - private void downloadAuthedUserCertificate() { - providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler()); - providerAPI_result_receiver.setReceiver(this); - - Intent provider_API_command = new Intent(this, ProviderAPI.class); - - Bundle parameters = new Bundle(); - parameters.putString(ConfigurationWizard.TYPE_OF_CERTIFICATE, ConfigurationWizard.AUTHED_CERTIFICATE); - - provider_API_command.setAction(ProviderAPI.DOWNLOAD_CERTIFICATE); - provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); - provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, providerAPI_result_receiver); - - startService(provider_API_command); + @Override + public void onReceiveResult(int resultCode, Bundle resultData) { + Log.d(TAG, "onReceiveResult"); + if(resultCode == ProviderAPI.SUCCESSFUL_SIGNUP) { + String username = resultData.getString(SessionDialog.USERNAME); + String password = resultData.getString(SessionDialog.PASSWORD); + logIn(username, password); + } else if(resultCode == ProviderAPI.FAILED_SIGNUP) { + updateViewHidingProgressBar(resultCode); + sessionDialog(resultData); + } else if(resultCode == ProviderAPI.SUCCESSFUL_LOGIN) { + authed_eip = true; + preferences.edit().putBoolean(Constants.AUTHED_EIP, authed_eip).apply(); + + updateViewHidingProgressBar(resultCode); + downloadAuthedUserCertificate(); + } else if(resultCode == ProviderAPI.FAILED_LOGIN) { + updateViewHidingProgressBar(resultCode); + sessionDialog(resultData); + } else if(resultCode == ProviderAPI.SUCCESSFUL_LOGOUT) { + authed_eip = false; + preferences.edit().putBoolean(Constants.AUTHED_EIP, authed_eip).apply(); + + updateViewHidingProgressBar(resultCode); + if(switching_provider) switchProvider(); + } else if(resultCode == ProviderAPI.LOGOUT_FAILED) { + updateViewHidingProgressBar(resultCode); + setResult(RESULT_CANCELED); + } else if(resultCode == ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE) { + updateViewHidingProgressBar(resultCode); + updateEipService(); + setResult(RESULT_OK); + } else if(resultCode == ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE) { + updateViewHidingProgressBar(resultCode); + setResult(RESULT_CANCELED); } + else if(resultCode == ProviderAPI.CORRECTLY_DOWNLOADED_EIP_SERVICE) { + setResult(RESULT_OK); - @Override - public void onReceiveResult(int resultCode, Bundle resultData) { - Log.d(TAG, "onReceiveResult"); - if(resultCode == ProviderAPI.SUCCESSFUL_SIGNUP) { - String username = resultData.getString(SessionDialog.USERNAME); - String password = resultData.getString(SessionDialog.PASSWORD); - logIn(username, password); - } else if(resultCode == ProviderAPI.FAILED_SIGNUP) { - changeStatusMessage(resultCode); - hideProgressBar(); - - signUpDialog(resultData); - } else if(resultCode == ProviderAPI.SUCCESSFUL_LOGIN) { - changeStatusMessage(resultCode); - hideProgressBar(); - - invalidateOptionsMenu(); - - authed_eip = true; - preferences.edit().putBoolean(Constants.AUTHED_EIP, authed_eip).apply(); - - downloadAuthedUserCertificate(); - } else if(resultCode == ProviderAPI.FAILED_LOGIN) { - changeStatusMessage(resultCode); - hideProgressBar(); - - logInDialog(resultData); - } else if(resultCode == ProviderAPI.SUCCESSFUL_LOGOUT) { - changeStatusMessage(resultCode); - hideProgressBar(); - - invalidateOptionsMenu(); - - authed_eip = false; - preferences.edit().putBoolean(Constants.AUTHED_EIP, authed_eip).apply(); - - } else if(resultCode == ProviderAPI.LOGOUT_FAILED) { - changeStatusMessage(resultCode); - hideProgressBar(); - - setResult(RESULT_CANCELED); - } else if(resultCode == ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE) { - changeStatusMessage(resultCode); - hideProgressBar(); - - setResult(RESULT_OK); - - updateEipService(); - } else if(resultCode == ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE) { - changeStatusMessage(resultCode); - hideProgressBar(); - setResult(RESULT_CANCELED); - } - else if(resultCode == ProviderAPI.CORRECTLY_DOWNLOADED_EIP_SERVICE) { - setResult(RESULT_OK); + updateEipService(); + } else if(resultCode == ProviderAPI.INCORRECTLY_DOWNLOADED_EIP_SERVICE) { + setResult(RESULT_CANCELED); + } + } - updateEipService(); - } else if(resultCode == ProviderAPI.INCORRECTLY_DOWNLOADED_EIP_SERVICE) { - setResult(RESULT_CANCELED); + private void updateViewHidingProgressBar(int resultCode) { + changeStatusMessage(resultCode); + hideProgressBar(); + invalidateOptionsMenu(); } - } private void updateEipService() { Intent updateEIP = new Intent(getApplicationContext(), EIP.class); updateEIP.setAction(Constants.ACTION_UPDATE_EIP_SERVICE); - ResultReceiver receiver = new ResultReceiver(new Handler()) { - protected void onReceiveResult(int resultCode, Bundle resultData) { + startService(updateEIP); + } + + private void changeStatusMessage(final int previous_result_code) { + ResultReceiver status_receiver = new ResultReceiver(new Handler()){ + protected void onReceiveResult(int resultCode, Bundle resultData){ + super.onReceiveResult(resultCode, resultData); String request = resultData.getString(Constants.REQUEST_TAG); - if(request.equalsIgnoreCase(Constants.ACTION_UPDATE_EIP_SERVICE)) { - if(resultCode == Activity.RESULT_OK) { - if(authed_eip && eip_fragment != null) eip_fragment.startEipFromScratch(); + if (request.equalsIgnoreCase(Constants.ACTION_IS_EIP_RUNNING)){ + if (resultCode == Activity.RESULT_OK){ + switch(previous_result_code){ + case ProviderAPI.SUCCESSFUL_LOGIN: setStatusMessage(R.string.succesful_authentication_message); break; + case ProviderAPI.FAILED_LOGIN: setStatusMessage(R.string.authentication_failed_message); break; + case ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE: setStatusMessage(R.string.authed_secured_status); break; + case ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE: setStatusMessage(R.string.incorrectly_downloaded_certificate_message); break; + case ProviderAPI.SUCCESSFUL_LOGOUT: setStatusMessage(R.string.logged_out_message); break; + case ProviderAPI.LOGOUT_FAILED: setStatusMessage(R.string.log_out_failed_message); break; + + } + } + else if(resultCode == Activity.RESULT_CANCELED){ + switch(previous_result_code){ + case ProviderAPI.SUCCESSFUL_LOGIN: setStatusMessage(R.string.succesful_authentication_message); break; + case ProviderAPI.FAILED_LOGIN: setStatusMessage(R.string.authentication_failed_message); break; + case ProviderAPI.FAILED_SIGNUP: setStatusMessage(R.string.registration_failed_message); break; + case ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE: break; + case ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE: setStatusMessage(R.string.incorrectly_downloaded_certificate_message); break; + case ProviderAPI.SUCCESSFUL_LOGOUT: setStatusMessage(R.string.logged_out_message); break; + case ProviderAPI.LOGOUT_FAILED: setStatusMessage(R.string.log_out_failed_message); break; + } } } + } }; - //updateEIP.putExtra(Constants.RECEIVER_TAG, receiver); - startService(updateEIP); + eipIsRunning(status_receiver); } - private void changeStatusMessage(final int previous_result_code) { - // TODO Auto-generated method stub - ResultReceiver eip_status_receiver = new ResultReceiver(new Handler()){ - protected void onReceiveResult(int resultCode, Bundle resultData){ - super.onReceiveResult(resultCode, resultData); - String request = resultData.getString(Constants.REQUEST_TAG); - if (request.equalsIgnoreCase(Constants.ACTION_IS_EIP_RUNNING)){ - if (resultCode == Activity.RESULT_OK){ - - switch(previous_result_code){ - case ProviderAPI.SUCCESSFUL_LOGIN: eip_fragment.status_message.setText(R.string.succesful_authentication_message); break; - case ProviderAPI.FAILED_LOGIN: eip_fragment.status_message.setText(R.string.authentication_failed_message); break; - case ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE: eip_fragment.status_message.setText(R.string.authed_secured_status); break; - case ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE: eip_fragment.status_message.setText(R.string.incorrectly_downloaded_certificate_message); break; - case ProviderAPI.SUCCESSFUL_LOGOUT: eip_fragment.status_message.setText(R.string.logged_out_message); break; - case ProviderAPI.LOGOUT_FAILED: eip_fragment.status_message.setText(R.string.log_out_failed_message); break; - - } - } - else if(resultCode == Activity.RESULT_CANCELED){ - - switch(previous_result_code){ - - case ProviderAPI.SUCCESSFUL_LOGIN: eip_fragment.status_message.setText(R.string.succesful_authentication_message); break; - case ProviderAPI.FAILED_LOGIN: eip_fragment.status_message.setText(R.string.authentication_failed_message); break; - case ProviderAPI.FAILED_SIGNUP: eip_fragment.status_message.setText(R.string.registration_failed_message); break; - case ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE: break; - case ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE: eip_fragment.status_message.setText(R.string.incorrectly_downloaded_certificate_message); break; - case ProviderAPI.SUCCESSFUL_LOGOUT: eip_fragment.status_message.setText(R.string.logged_out_message); break; - case ProviderAPI.LOGOUT_FAILED: eip_fragment.status_message.setText(R.string.log_out_failed_message); break; - } - } - } - - } - }; - eipIsRunning(eip_status_receiver); - } + private void setStatusMessage(int string_resId) { + if(eip_fragment != null && eip_fragment.status_message != null) + eip_fragment.status_message.setText(string_resId); + } + + private void eipIsRunning(ResultReceiver eip_receiver){ + // TODO validate "action"...how do we get the list of intent-filters for a class via Android API? + Intent intent = new Intent(this, EIP.class); + intent.setAction(Constants.ACTION_IS_EIP_RUNNING); + intent.putExtra(Constants.RECEIVER_TAG, eip_receiver); + startService(intent); + } private void hideProgressBar() { if(eip_fragment != null) { @@ -546,28 +468,13 @@ public class Dashboard extends Activity implements SessionDialog.SessionDialogIn } } - /** - * For retrieving the base application Context in classes that don't extend - * Android's Activity class - * - * @return Application Context as defined by <code>this</code> for Dashboard instance - */ - public static Context getAppContext() { - return app; - } - - + public static Context getAppContext() { + return app; + } + @Override public void startActivityForResult(Intent intent, int requestCode) { intent.putExtra(Dashboard.REQUEST_CODE, requestCode); super.startActivityForResult(intent, requestCode); } - - private void eipIsRunning(ResultReceiver eip_receiver){ - // TODO validate "action"...how do we get the list of intent-filters for a class via Android API? - Intent eip_intent = new Intent(this, EIP.class); - eip_intent.setAction(Constants.ACTION_IS_EIP_RUNNING); - eip_intent.putExtra(Constants.RECEIVER_TAG, eip_receiver); - startService(eip_intent); - } } diff --git a/app/src/main/java/se/leap/bitmaskclient/EipFragment.java b/app/src/main/java/se/leap/bitmaskclient/EipFragment.java index 420da7a1..54432033 100644 --- a/app/src/main/java/se/leap/bitmaskclient/EipFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/EipFragment.java @@ -56,8 +56,7 @@ public class EipFragment extends Fragment implements Observer { parent_activity = activity; Dashboard dashboard = (Dashboard) parent_activity; - Intent provider_API_command = dashboard.prepareProviderAPICommand(); - provider_API_command.setAction(ProviderAPI.DOWNLOAD_EIP_SERVICE); + Intent provider_API_command = dashboard.prepareProviderAPICommand(Bundle.EMPTY, ProviderAPI.DOWNLOAD_EIP_SERVICE); parent_activity.startService(provider_API_command); } @@ -110,15 +109,10 @@ public class EipFragment extends Fragment implements Observer { super.onSaveInstanceState(outState); } - protected void saveEipStatus() { - boolean eip_is_on = false; - Log.d(TAG, "saveEipStatus"); - if(eip_switch.isChecked()) { - eip_is_on = true; - } - - if(parent_activity != null) - Dashboard.preferences.edit().putBoolean(Dashboard.START_ON_BOOT, eip_is_on).commit(); + protected void saveStatus() { + boolean is_on = eip_switch.isChecked(); + Log.d(TAG, "saveStatus: is_on = " + is_on); + Dashboard.preferences.edit().putBoolean(Dashboard.START_ON_BOOT, is_on).commit(); } @OnCheckedChanged(R.id.eipSwitch) @@ -128,7 +122,7 @@ public class EipFragment extends Fragment implements Observer { else handleSwitchOff(); - saveEipStatus(); + saveStatus(); } private void handleSwitchOn() { @@ -139,7 +133,7 @@ public class EipFragment extends Fragment implements Observer { Dashboard dashboard = (Dashboard) parent_activity; Bundle bundle = new Bundle(); bundle.putBoolean(IS_PENDING, true); - dashboard.logInDialog(bundle); + dashboard.sessionDialog(bundle); } } @@ -193,8 +187,8 @@ public class EipFragment extends Fragment implements Observer { if(!eip_switch.isChecked()) { eip_switch.setChecked(true); - saveEipStatus(); } + saveStatus(); eipCommand(Constants.ACTION_START_EIP); } @@ -211,6 +205,7 @@ public class EipFragment extends Fragment implements Observer { String status = parent_activity.getString(R.string.eip_state_not_connected); status_message.setText(status); + eipCommand(Constants.ACTION_STOP_EIP); } @@ -343,10 +338,9 @@ public class EipFragment extends Fragment implements Observer { progress_bar.setVisibility(View.VISIBLE); status_message.setText(getString(R.string.updating_certificate_message)); if(LeapSRPSession.getToken().isEmpty() && !Dashboard.preferences.getBoolean(Constants.ALLOWED_ANON, false)) { - dashboard.logInDialog(Bundle.EMPTY); + dashboard.sessionDialog(Bundle.EMPTY); } else { - Intent provider_API_command = dashboard.prepareProviderAPICommand(); - provider_API_command.setAction(ProviderAPI.DOWNLOAD_CERTIFICATE); + Intent provider_API_command = dashboard.prepareProviderAPICommand(Bundle.EMPTY, ProviderAPI.DOWNLOAD_CERTIFICATE); parent_activity.startService(provider_API_command); } break; diff --git a/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java b/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java index 07ed6c8f..96b87085 100644 --- a/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java +++ b/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java @@ -3,22 +3,29 @@ package se.leap.bitmaskclient; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; +import android.util.Log; import se.leap.bitmaskclient.eip.Constants; public class OnBootReceiver extends BroadcastReceiver { - // Debug: am broadcast -a android.intent.action.BOOT_COMPLETED - @Override - public void onReceive(Context context, Intent intent) { - if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { - if (!context.getSharedPreferences(Dashboard.SHARED_PREFERENCES, Context.MODE_PRIVATE).getString(Provider.KEY, "").isEmpty() && context.getSharedPreferences(Dashboard.SHARED_PREFERENCES, Context.MODE_PRIVATE).getBoolean(Dashboard.START_ON_BOOT, false)) { - Intent dashboard_intent = new Intent(context, Dashboard.class); - dashboard_intent.setAction(Constants.ACTION_START_EIP); - dashboard_intent.putExtra(Dashboard.ON_BOOT, true); - dashboard_intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(dashboard_intent); - } - } + SharedPreferences preferences; + + // Debug: am broadcast -a android.intent.action.BOOT_COMPLETED + @Override + public void onReceive(Context context, Intent intent) { + preferences = context.getSharedPreferences(Dashboard.SHARED_PREFERENCES, Context.MODE_PRIVATE); + boolean provider_configured = !preferences.getString(Provider.KEY, "").isEmpty(); + boolean start_on_boot = preferences.getBoolean(Dashboard.START_ON_BOOT, false); + Log.d("OnBootReceiver", "Provider configured " + String.valueOf(provider_configured)); + Log.d("OnBootReceiver", "Start on boot " + String.valueOf(start_on_boot)); + if(provider_configured && start_on_boot) { + Intent dashboard_intent = new Intent(context, Dashboard.class); + dashboard_intent.setAction(Constants.ACTION_START_EIP); + dashboard_intent.putExtra(Dashboard.ON_BOOT, true); + dashboard_intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(dashboard_intent); } + } } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java index 3d3070c8..533b0281 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java @@ -25,19 +25,27 @@ import android.os.Bundle; import android.os.ResultReceiver; import android.util.Log; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; +import java.util.Iterator; import java.util.List; +import java.util.NoSuchElementException; import de.blinkt.openvpn.LaunchVPN; import de.blinkt.openvpn.VpnProfile; +import de.blinkt.openvpn.core.Connection; import de.blinkt.openvpn.core.ProfileManager; import se.leap.bitmaskclient.Dashboard; import se.leap.bitmaskclient.EipFragment; +import se.leap.bitmaskclient.Provider; import static se.leap.bitmaskclient.eip.Constants.ACTION_CHECK_CERT_VALIDITY; import static se.leap.bitmaskclient.eip.Constants.ACTION_IS_EIP_RUNNING; @@ -71,7 +79,7 @@ public final class EIP extends IntentService { private static SharedPreferences preferences; private static JSONObject eip_definition; - private static List<Gateway> gateways = new ArrayList<Gateway>(); + private static List<Gateway> gateways = new ArrayList<>(); private static ProfileManager profile_manager; private static Gateway gateway; @@ -84,12 +92,14 @@ public final class EIP extends IntentService { super.onCreate(); context = getApplicationContext(); - profile_manager = ProfileManager.getInstance(context); + preferences = getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE); - preferences = getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE); - refreshEipDefinition(); + profile_manager = ProfileManager.getInstance(context); + eip_definition = eipDefinitionFromPreferences(); + if(gateways.isEmpty()) + gateways = gatewaysFromPreferences(); } - + @Override protected void onHandleIntent(Intent intent) { String action = intent.getAction(); @@ -101,7 +111,7 @@ public final class EIP extends IntentService { stopEIP(); else if (action.equals(ACTION_IS_EIP_RUNNING)) isRunning(); - else if (action.equals(ACTION_UPDATE_EIP_SERVICE)) + else if (action.equals(ACTION_UPDATE_EIP_SERVICE)) updateEIPService(); else if (action.equals(ACTION_CHECK_CERT_VALIDITY)) checkCertValidity(); @@ -122,8 +132,9 @@ public final class EIP extends IntentService { if(gateway != null && gateway.getProfile() != null) { mReceiver = EipFragment.getReceiver(); launchActiveGateway(); - } - tellToReceiver(ACTION_START_EIP, Activity.RESULT_OK); + tellToReceiver(ACTION_START_EIP, Activity.RESULT_OK); + } else + tellToReceiver(ACTION_START_EIP, Activity.RESULT_CANCELED); } /** @@ -147,7 +158,6 @@ public final class EIP extends IntentService { private void stopEIP() { EipStatus eip_status = EipStatus.getInstance(); - Log.d(TAG, "stopEip(): eip is connected? " + eip_status.isConnected()); int result_code = Activity.RESULT_CANCELED; if(eip_status.isConnected() || eip_status.isConnecting()) result_code = Activity.RESULT_OK; @@ -173,46 +183,55 @@ public final class EIP extends IntentService { * TODO Implement API call to refresh eip-service.json from the provider */ private void updateEIPService() { - refreshEipDefinition(); - deleteAllVpnProfiles(); - updateGateways(); + eip_definition = eipDefinitionFromPreferences(); + if(eip_definition != null) + updateGateways(); tellToReceiver(ACTION_UPDATE_EIP_SERVICE, Activity.RESULT_OK); } - private void refreshEipDefinition() { + private JSONObject eipDefinitionFromPreferences() { try { String eip_definition_string = preferences.getString(KEY, ""); if(!eip_definition_string.isEmpty()) { - eip_definition = new JSONObject(eip_definition_string); + return new JSONObject(eip_definition_string); } } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } + return null; } - - private void deleteAllVpnProfiles() { - Collection<VpnProfile> profiles = profile_manager.getProfiles(); - profiles.removeAll(profiles); - gateways.clear(); + + private List<Gateway> gatewaysFromPreferences() { + List<Gateway> result; + + String gateways_string = preferences.getString(Gateway.TAG, ""); + Type type_list_gateways = new TypeToken<ArrayList<Gateway>>() {}.getType(); + result = gateways_string.isEmpty() ? + new ArrayList<Gateway>() + : (List<Gateway>) new Gson().fromJson(gateways_string, type_list_gateways); + preferences.edit().remove(Gateway.TAG); + return result; } /** * Walk the list of gateways defined in eip-service.json and parse them into * Gateway objects. - * TODO Store the Gateways (as Serializable) in SharedPreferences */ private void updateGateways(){ try { - if(eip_definition != null) { JSONArray gatewaysDefined = eip_definition.getJSONArray("gateways"); for (int i = 0; i < gatewaysDefined.length(); i++) { JSONObject gw = gatewaysDefined.getJSONObject(i); if (isOpenVpnGateway(gw)) { - addGateway(new Gateway(eip_definition, context, gw)); + JSONObject secrets = secretsConfiguration(); + Gateway aux = new Gateway(eip_definition, secrets, gw); + if(!containsProfileWithSecrets(aux.getProfile())) { + addGateway(aux); + } } } - } + gatewaysToPreferences(); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -228,11 +247,112 @@ public final class EIP extends IntentService { } } + + private JSONObject secretsConfiguration() { + JSONObject result = new JSONObject(); + try { + result.put(Provider.CA_CERT, preferences.getString(Provider.CA_CERT, "")); + result.put(Constants.PRIVATE_KEY, preferences.getString(Constants.PRIVATE_KEY, "")); + result.put(Constants.CERTIFICATE, preferences.getString(Constants.CERTIFICATE, "")); + } catch (JSONException e) { + e.printStackTrace(); + } + return result; + } + private void addGateway(Gateway gateway) { - profile_manager.addProfile(gateway.getProfile()); + VpnProfile profile = gateway.getProfile(); + removeGateway(gateway); + + profile_manager.addProfile(profile); + profile_manager.saveProfile(context, profile); + profile_manager.saveProfileList(context); + gateways.add(gateway); } + private void removeGateway(Gateway gateway) { + VpnProfile profile = gateway.getProfile(); + removeDuplicatedProfile(profile); + removeDuplicatedGateway(profile); + } + + private void removeDuplicatedProfile(VpnProfile original) { + if(containsProfile(original)) { + VpnProfile remove = duplicatedProfile(original); + profile_manager.removeProfile(context, remove); + }if(containsProfile(original)) removeDuplicatedProfile(original); + } + + private boolean containsProfile(VpnProfile profile) { + Collection<VpnProfile> profiles = profile_manager.getProfiles(); + for(VpnProfile aux : profiles) { + if (sameConnections(profile.mConnections, aux.mConnections)) { + return true; + } + } + return false; + } + + private boolean containsProfileWithSecrets(VpnProfile profile) { + boolean result = false; + + if(containsProfile(profile)) { + Collection<VpnProfile> profiles = profile_manager.getProfiles(); + for(VpnProfile aux : profiles) { + result = result == false ? + sameConnections(profile.mConnections, aux.mConnections) + && profile.mClientCertFilename.equalsIgnoreCase(aux.mClientCertFilename) + && profile.mClientKeyFilename.equalsIgnoreCase(aux.mClientKeyFilename) + : true; + } + } + return result; + } + + private VpnProfile duplicatedProfile(VpnProfile profile) { + VpnProfile duplicated = null; + Collection<VpnProfile> profiles = profile_manager.getProfiles(); + for(VpnProfile aux : profiles) { + if (sameConnections(profile.mConnections, aux.mConnections)) { + duplicated = aux; + } + } + if(duplicated != null) return duplicated; + else throw new NoSuchElementException(profile.getName()); + } + + private boolean sameConnections(Connection[] c1, Connection[] c2) { + int same_connections = 0; + for(Connection c1_aux : c1) { + for(Connection c2_aux : c2) + if(c2_aux.mServerName.equals(c1_aux.mServerName)) { + same_connections++; + break; + } + } + return c1.length == c2.length && c1.length == same_connections; + + } + + private void removeDuplicatedGateway(VpnProfile profile) { + Iterator<Gateway> it = gateways.iterator(); + List<Gateway> gateways_to_remove = new ArrayList<>(); + while(it.hasNext()) { + Gateway aux = it.next(); + if(sameConnections(aux.getProfile().mConnections, profile.mConnections)) { + gateways_to_remove.add(aux); + } + } + gateways.removeAll(gateways_to_remove); + } + + private void gatewaysToPreferences() { + Type type_list_gateways = new TypeToken<List<Gateway>>() {}.getType(); + String gateways_string = new Gson().toJson(gateways, type_list_gateways); + preferences.edit().putString(Gateway.TAG, gateways_string).apply(); + } + private void checkCertValidity() { VpnCertificateValidator validator = new VpnCertificateValidator(); int resultCode = validator.isValid(preferences.getString(CERTIFICATE, "")) ? diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java index 3ee9443c..daf7d4a7 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java @@ -17,7 +17,6 @@ package se.leap.bitmaskclient.eip; import android.app.Activity; -import android.content.Context; import android.content.SharedPreferences; import android.util.Log; @@ -26,12 +25,9 @@ import org.json.JSONObject; import java.io.IOException; import java.io.StringReader; -import java.util.Collection; -import java.util.Iterator; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.ConfigParser; -import de.blinkt.openvpn.core.ProfileManager; import se.leap.bitmaskclient.Dashboard; /** @@ -44,48 +40,30 @@ import se.leap.bitmaskclient.Dashboard; */ public class Gateway { - private String TAG = Gateway.class.getSimpleName(); - + public static String TAG = Gateway.class.getSimpleName(); + + private JSONObject general_configuration; + private JSONObject secrets; + private JSONObject gateway; + private String mName; private int timezone; - private JSONObject general_configuration; - private Context context; private VpnProfile mVpnProfile; - private JSONObject mGateway; - /** * Build a gateway object from a JSON OpenVPN gateway definition in eip-service.json * and create a VpnProfile belonging to it. - * - * @param gateway The JSON OpenVPN gateway definition to parse */ - protected Gateway(JSONObject eip_definition, Context context, JSONObject gateway){ + protected Gateway(JSONObject eip_definition, JSONObject secrets, JSONObject gateway){ + + this.gateway = gateway; + this.secrets = secrets; - mGateway = gateway; - - this.context = context; general_configuration = getGeneralConfiguration(eip_definition); timezone = getTimezone(eip_definition); mName = locationAsName(eip_definition); - // Currently deletes VpnProfile for host, if there already is one, and builds new - ProfileManager vpl = ProfileManager.getInstance(context); - Collection<VpnProfile> profiles = vpl.getProfiles(); - for (Iterator<VpnProfile> it = profiles.iterator(); it.hasNext(); ){ - VpnProfile p = it.next(); - - if ( p.mName.equalsIgnoreCase( mName ) ) { - it.remove(); - vpl.removeProfile(context, p); - } - } - mVpnProfile = createVPNProfile(); mVpnProfile.mName = mName; - - vpl.addProfile(mVpnProfile); - vpl.saveProfile(context, mVpnProfile); - vpl.saveProfileList(context); } private JSONObject getGeneralConfiguration(JSONObject eip_definition) { @@ -110,7 +88,7 @@ public class Gateway { try { JSONObject locations = eip_definition.getJSONObject("locations"); - return locations.getJSONObject(mGateway.getString("location")); + return locations.getJSONObject(gateway.getString("location")); } catch (JSONException e) { return new JSONObject(); } @@ -123,8 +101,7 @@ public class Gateway { try { ConfigParser cp = new ConfigParser(); - SharedPreferences preferences = context.getSharedPreferences(Dashboard.SHARED_PREFERENCES, Activity.MODE_PRIVATE); - VpnConfigGenerator vpn_configuration_generator = new VpnConfigGenerator(preferences, general_configuration, mGateway); + VpnConfigGenerator vpn_configuration_generator = new VpnConfigGenerator(general_configuration, secrets, gateway); String configuration = vpn_configuration_generator.generate(); cp.parseConfig(new StringReader(configuration)); diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java index 0c8e9a04..6f260f55 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java @@ -31,15 +31,15 @@ public class VpnConfigGenerator { private JSONObject general_configuration; private JSONObject gateway; - - private static SharedPreferences preferences; + private JSONObject secrets; + public final static String TAG = VpnConfigGenerator.class.getSimpleName(); private final String new_line = System.getProperty("line.separator"); // Platform new line - public VpnConfigGenerator(SharedPreferences preferences, JSONObject general_configuration, JSONObject gateway) { + public VpnConfigGenerator(JSONObject general_configuration, JSONObject secrets, JSONObject gateway) { this.general_configuration = general_configuration; this.gateway = gateway; - VpnConfigGenerator.preferences = preferences; + this.secrets = secrets; } public String generate() { @@ -79,59 +79,67 @@ public class VpnConfigGenerator { private String gatewayConfiguration() { String remotes = ""; - String remote = "ip_address"; - String remote_openvpn_keyword = "remote"; - String ports = "ports"; - String protos = "protocols"; - String capabilities = "capabilities"; + String ip_address_keyword = "ip_address"; + String remote_keyword = "remote"; + String ports_keyword = "ports"; + String protocol_keyword = "protocols"; + String capabilities_keyword = "capabilities"; String udp = "udp"; try { - JSONArray protocolsJSON = gateway.getJSONObject(capabilities).getJSONArray(protos); - for ( int i=0; i<protocolsJSON.length(); i++ ) { - String remote_line = remote_openvpn_keyword; - remote_line += " " + gateway.getString(remote); - remote_line += " " + gateway.getJSONObject(capabilities).getJSONArray(ports).optString(0); - remote_line += " " + protocolsJSON.optString(i); - if(remote_line.endsWith(udp)) - remotes = remotes.replaceFirst(remote_openvpn_keyword, remote_line + new_line + remote_openvpn_keyword); - else - remotes += remote_line; - remotes += new_line; + String ip_address = gateway.getString(ip_address_keyword); + JSONObject capabilities = gateway.getJSONObject(capabilities_keyword); + JSONArray ports = capabilities.getJSONArray(ports_keyword); + for (int i=0; i<ports.length(); i++) { + String port_specific_remotes = ""; + int port = ports.getInt(i); + JSONArray protocols = capabilities.getJSONArray(protocol_keyword); + for ( int j=0; j<protocols.length(); j++ ) { + String protocol = protocols.optString(j); + String new_remote = remote_keyword + " " + ip_address + " " + port + " " + protocol + new_line; + + port_specific_remotes = protocol.equalsIgnoreCase(udp) ? + port_specific_remotes.replaceFirst(remote_keyword, new_remote + new_line + remote_keyword) : + new_remote; + } + remotes += port_specific_remotes; } } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } - Log.d(TAG, "remotes = " + remotes); return remotes; } private String secretsConfiguration() { - - String ca = - "<ca>" - + new_line - + preferences.getString(Provider.CA_CERT, "") - + new_line - + "</ca>"; - - String key = - "<key>" - + new_line - + preferences.getString(Constants.PRIVATE_KEY, "") - + new_line - + "</key>"; - - String openvpn_cert = - "<cert>" - + new_line - + preferences.getString(Constants.CERTIFICATE, "") - + new_line - + "</cert>"; + try { + String ca = + "<ca>" + + new_line + + secrets.getString(Provider.CA_CERT) + + new_line + + "</ca>"; + + String key = + "<key>" + + new_line + + secrets.getString(Constants.PRIVATE_KEY) + + new_line + + "</key>"; + + String openvpn_cert = + "<cert>" + + new_line + + secrets.getString(Constants.CERTIFICATE) + + new_line + + "</cert>"; - return ca + new_line + key + new_line + openvpn_cert; + return ca + new_line + key + new_line + openvpn_cert; + } catch(JSONException e) { + e.printStackTrace(); + return ""; + } } private String androidCustomizations() { |