summaryrefslogtreecommitdiff
path: root/src/de/blinkt/openvpn/core
diff options
context:
space:
mode:
authorArne Schwabe <arne@rfc2549.org>2013-03-12 14:18:53 +0100
committerArne Schwabe <arne@rfc2549.org>2013-03-12 14:18:53 +0100
commitef4438a0ede0394736f8abdbcf4fa24b712ec7eb (patch)
treea0ea8ba4002e2063438a2dcc7123dfa98f0fc7ab /src/de/blinkt/openvpn/core
parent94e4d391a1f2731a132ccc70dbe98a7cbb458274 (diff)
Break the classes into core classes that deal with handling the backend logic, the fragment and rest.
LaunchVPN, MainActivity, ConfigConvert are public API that is recorded in shortcuts, launchers etc. Moving them would break public API. Also VPNProfile belongs into core but since the reading VPN Profiles depends on the class name keep it in the main package. --HG-- rename : src/de/blinkt/openvpn/CIDRIP.java => src/de/blinkt/openvpn/core/CIDRIP.java rename : src/de/blinkt/openvpn/ConfigParser.java => src/de/blinkt/openvpn/core/ConfigParser.java rename : src/de/blinkt/openvpn/NetworkSateReceiver.java => src/de/blinkt/openvpn/core/NetworkSateReceiver.java rename : src/de/blinkt/openvpn/OpenVPN.java => src/de/blinkt/openvpn/core/OpenVPN.java rename : src/de/blinkt/openvpn/OpenVPNMangement.java => src/de/blinkt/openvpn/core/OpenVPNMangement.java rename : src/de/blinkt/openvpn/OpenVPNThread.java => src/de/blinkt/openvpn/core/OpenVPNThread.java rename : src/de/blinkt/openvpn/OpenVpnManagementThread.java => src/de/blinkt/openvpn/core/OpenVpnManagementThread.java rename : src/de/blinkt/openvpn/OpenVpnService.java => src/de/blinkt/openvpn/core/OpenVpnService.java rename : src/de/blinkt/openvpn/ProfileManager.java => src/de/blinkt/openvpn/core/ProfileManager.java rename : src/de/blinkt/openvpn/ProxyDetection.java => src/de/blinkt/openvpn/core/ProxyDetection.java rename : src/de/blinkt/openvpn/VPNLaunchHelper.java => src/de/blinkt/openvpn/core/VPNLaunchHelper.java rename : src/de/blinkt/openvpn/AboutFragment.java => src/de/blinkt/openvpn/fragments/AboutFragment.java rename : src/de/blinkt/openvpn/FaqFragment.java => src/de/blinkt/openvpn/fragments/FaqFragment.java rename : src/de/blinkt/openvpn/FileSelectionFragment.java => src/de/blinkt/openvpn/fragments/FileSelectionFragment.java rename : src/de/blinkt/openvpn/GeneralSettings.java => src/de/blinkt/openvpn/fragments/GeneralSettings.java rename : src/de/blinkt/openvpn/InlineFileTab.java => src/de/blinkt/openvpn/fragments/InlineFileTab.java rename : src/de/blinkt/openvpn/OpenVpnPreferencesFragment.java => src/de/blinkt/openvpn/fragments/OpenVpnPreferencesFragment.java rename : src/de/blinkt/openvpn/SendDumpFragment.java => src/de/blinkt/openvpn/fragments/SendDumpFragment.java rename : src/de/blinkt/openvpn/Settings_Authentication.java => src/de/blinkt/openvpn/fragments/Settings_Authentication.java rename : src/de/blinkt/openvpn/Settings_Basic.java => src/de/blinkt/openvpn/fragments/Settings_Basic.java rename : src/de/blinkt/openvpn/Settings_IP.java => src/de/blinkt/openvpn/fragments/Settings_IP.java rename : src/de/blinkt/openvpn/Settings_Obscure.java => src/de/blinkt/openvpn/fragments/Settings_Obscure.java rename : src/de/blinkt/openvpn/Settings_Routing.java => src/de/blinkt/openvpn/fragments/Settings_Routing.java rename : src/de/blinkt/openvpn/ShowConfigFragment.java => src/de/blinkt/openvpn/fragments/ShowConfigFragment.java rename : src/de/blinkt/openvpn/VPNProfileList.java => src/de/blinkt/openvpn/fragments/VPNProfileList.java
Diffstat (limited to 'src/de/blinkt/openvpn/core')
-rw-r--r--src/de/blinkt/openvpn/core/CIDRIP.java66
-rw-r--r--src/de/blinkt/openvpn/core/ConfigParser.java640
-rw-r--r--src/de/blinkt/openvpn/core/NetworkSateReceiver.java90
-rw-r--r--src/de/blinkt/openvpn/core/OpenVPN.java427
-rw-r--r--src/de/blinkt/openvpn/core/OpenVPNMangement.java14
-rw-r--r--src/de/blinkt/openvpn/core/OpenVPNThread.java136
-rw-r--r--src/de/blinkt/openvpn/core/OpenVpnManagementThread.java482
-rw-r--r--src/de/blinkt/openvpn/core/OpenVpnService.java625
-rw-r--r--src/de/blinkt/openvpn/core/ProfileManager.java223
-rw-r--r--src/de/blinkt/openvpn/core/ProxyDetection.java55
-rw-r--r--src/de/blinkt/openvpn/core/VPNLaunchHelper.java76
11 files changed, 2834 insertions, 0 deletions
diff --git a/src/de/blinkt/openvpn/core/CIDRIP.java b/src/de/blinkt/openvpn/core/CIDRIP.java
new file mode 100644
index 00000000..27ce414c
--- /dev/null
+++ b/src/de/blinkt/openvpn/core/CIDRIP.java
@@ -0,0 +1,66 @@
+package de.blinkt.openvpn.core;
+
+import java.util.Locale;
+
+class CIDRIP{
+ String mIp;
+ int len;
+
+
+ public CIDRIP(String ip, String mask){
+ mIp=ip;
+ long netmask=getInt(mask);
+
+ // Add 33. bit to ensure the loop terminates
+ netmask += 1l << 32;
+
+ int lenZeros = 0;
+ while((netmask & 0x1) == 0) {
+ lenZeros++;
+ netmask = netmask >> 1;
+ }
+ // Check if rest of netmask is only 1s
+ if(netmask != (0x1ffffffffl >> lenZeros)) {
+ // Asume no CIDR, set /32
+ len=32;
+ } else {
+ len =32 -lenZeros;
+ }
+
+ }
+ public CIDRIP(String address, int prefix_length) {
+ len = prefix_length;
+ mIp = address;
+ }
+ @Override
+ public String toString() {
+ return String.format(Locale.ENGLISH,"%s/%d",mIp,len);
+ }
+
+ public boolean normalise(){
+ long ip=getInt(mIp);
+
+ long newip = ip & (0xffffffffl << (32 -len));
+ if (newip != ip){
+ mIp = String.format("%d.%d.%d.%d", (newip & 0xff000000) >> 24,(newip & 0xff0000) >> 16, (newip & 0xff00) >> 8 ,newip & 0xff);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ static long getInt(String ipaddr) {
+ String[] ipt = ipaddr.split("\\.");
+ long ip=0;
+
+ ip += Long.parseLong(ipt[0])<< 24;
+ ip += Integer.parseInt(ipt[1])<< 16;
+ ip += Integer.parseInt(ipt[2])<< 8;
+ ip += Integer.parseInt(ipt[3]);
+
+ return ip;
+ }
+ public long getInt() {
+ return getInt(mIp);
+ }
+
+} \ No newline at end of file
diff --git a/src/de/blinkt/openvpn/core/ConfigParser.java b/src/de/blinkt/openvpn/core/ConfigParser.java
new file mode 100644
index 00000000..9faebfb6
--- /dev/null
+++ b/src/de/blinkt/openvpn/core/ConfigParser.java
@@ -0,0 +1,640 @@
+package de.blinkt.openvpn.core;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Vector;
+
+import de.blinkt.openvpn.VpnProfile;
+
+//! Openvpn Config FIle Parser, probably not 100% accurate but close enough
+
+// And rember, this is valid :)
+// --<foo>
+// bar
+// </foo>
+public class ConfigParser {
+
+
+ public static final String CONVERTED_PROFILE = "converted Profile";
+ private HashMap<String, Vector<Vector<String>>> options = new HashMap<String, Vector<Vector<String>>>();
+ private HashMap<String, Vector<String>> meta = new HashMap<String, Vector<String>>();
+
+
+ private boolean extraRemotesAsCustom=false;
+
+ public void parseConfig(Reader reader) throws IOException, ConfigParseError {
+
+
+ BufferedReader br =new BufferedReader(reader);
+
+ while (true){
+ String line = br.readLine();
+ if(line==null)
+ break;
+
+ // 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.get(0).startsWith("--"))
+ args.set(0, args.get(0).substring(2));
+
+ checkinlinefile(args,br);
+
+ String optionname = args.get(0);
+ if(!options.containsKey(optionname)) {
+ options.put(optionname, new Vector<Vector<String>>());
+ }
+ options.get(optionname).add(args);
+ }
+ }
+
+ private Vector<String> parsemeta(String line) {
+ String meta = line.split("#\\sOVPN_ACCESS_SERVER_", 2)[1];
+ String[] parts = meta.split("=",2);
+ Vector<String> rval = new Vector<String>();
+ for(String p:parts)
+ rval.add(p);
+ return rval;
+
+ }
+
+ private void checkinlinefile(Vector<String> args, BufferedReader br) throws IOException, ConfigParseError {
+ String arg0 = args.get(0);
+ // CHeck for <foo>
+ if(arg0.startsWith("<") && arg0.endsWith(">")) {
+ String argname = arg0.substring(1, arg0.length()-1);
+ String inlinefile = VpnProfile.INLINE_TAG;
+
+ String endtag = String.format("</%s>",argname);
+ do {
+ String line = br.readLine();
+ if(line==null){
+ throw new ConfigParseError(String.format("No endtag </%s> for starttag <%s> found",argname,argname));
+ }
+ if(line.equals(endtag))
+ break;
+ else {
+ inlinefile+=line;
+ inlinefile+= "\n";
+ }
+ } while(true);
+
+ args.clear();
+ args.add(argname);
+ args.add(inlinefile);
+ }
+
+ }
+
+ enum linestate {
+ initial,
+ readin_single_quote
+ , reading_quoted, reading_unquoted, done}
+
+ private boolean space(char c) {
+ // I really hope nobody is using zero bytes inside his/her config file
+ // to sperate parameter but here we go:
+ return Character.isWhitespace(c) || c == '\0';
+
+ }
+
+ public class ConfigParseError extends Exception {
+ private static final long serialVersionUID = -60L;
+
+ public ConfigParseError(String msg) {
+ super(msg);
+ }
+ }
+
+
+ // adapted openvpn's parse function to java
+ private Vector<String> parseline(String line) throws ConfigParseError {
+ Vector<String> parameters = new Vector<String>();
+
+ if (line.length()==0)
+ return parameters;
+
+
+ linestate state = linestate.initial;
+ boolean backslash = false;
+ char out=0;
+
+ int pos=0;
+ String currentarg="";
+
+ do {
+ // Emulate the c parsing ...
+ char in;
+ if(pos < line.length())
+ in = line.charAt(pos);
+ else
+ in = '\0';
+
+ if (!backslash && in == '\\' && state != linestate.readin_single_quote)
+ {
+ backslash = true;
+ }
+ else
+ {
+ if (state == linestate.initial)
+ {
+ if (!space (in))
+ {
+ if (in == ';' || in == '#') /* comment */
+ break;
+ if (!backslash && in == '\"')
+ state = linestate.reading_quoted;
+ else if (!backslash && in == '\'')
+ state = linestate.readin_single_quote;
+ else
+ {
+ out = in;
+ state = linestate.reading_unquoted;
+ }
+ }
+ }
+ else if (state == linestate.reading_unquoted)
+ {
+ if (!backslash && space (in))
+ state = linestate.done;
+ else
+ out = in;
+ }
+ else if (state == linestate.reading_quoted)
+ {
+ if (!backslash && in == '\"')
+ state = linestate.done;
+ else
+ out = in;
+ }
+ else if (state == linestate.readin_single_quote)
+ {
+ if (in == '\'')
+ state = linestate.done;
+ else
+ out = in;
+ }
+
+ if (state == linestate.done)
+ {
+ /* ASSERT (parm_len > 0); */
+ state = linestate.initial;
+ parameters.add(currentarg);
+ currentarg = "";
+ out =0;
+ }
+
+ if (backslash && out!=0)
+ {
+ if (!(out == '\\' || out == '\"' || space (out)))
+ {
+ throw new ConfigParseError("Options warning: Bad backslash ('\\') usage");
+ }
+ }
+ backslash = false;
+ }
+
+ /* store parameter character */
+ if (out!=0)
+ {
+ currentarg+=out;
+ }
+ } while (pos++ < line.length());
+
+ return parameters;
+ }
+
+
+ final String[] unsupportedOptions = { "config",
+ "connection",
+ "proto-force",
+ "remote-random",
+ "tls-server"
+
+ };
+
+ // Ignore all scripts
+ // in most cases these won't work and user who wish to execute scripts will
+ // figure out themselves
+ final String[] ignoreOptions = { "tls-client",
+ "askpass",
+ "auth-nocache",
+ "up",
+ "down",
+ "route-up",
+ "ipchange",
+ "route-up",
+ "route-pre-down",
+ "auth-user-pass-verify",
+ "dhcp-release",
+ "dhcp-renew",
+ "dh",
+ "ip-win32",
+ "management-hold",
+ "management",
+ "management-query-passwords",
+ "pause-exit",
+ "persist-key",
+ "register-dns",
+ "route-delay",
+ "route-gateway",
+ "route-metric",
+ "route-method",
+ "status",
+ "script-security",
+ "show-net-up",
+ "suppress-timestamps",
+ "tmp-dir",
+ "tun-ipv6",
+ "topology",
+ "win-sys",
+ };
+
+
+ // This method is far too long
+ public VpnProfile convertProfile() throws ConfigParseError{
+ boolean noauthtypeset=true;
+ VpnProfile np = new VpnProfile(CONVERTED_PROFILE);
+ // Pull, client, tls-client
+ np.clearDefaults();
+
+ if(options.containsKey("client") || options.containsKey("pull")) {
+ np.mUsePull=true;
+ options.remove("pull");
+ options.remove("client");
+ }
+
+ Vector<String> secret = getOption("secret", 1, 2);
+ if(secret!=null)
+ {
+ np.mAuthenticationType=VpnProfile.TYPE_STATICKEYS;
+ noauthtypeset=false;
+ np.mUseTLSAuth=true;
+ np.mTLSAuthFilename=secret.get(1);
+ if(secret.size()==3)
+ np.mTLSAuthDirection=secret.get(2);
+
+ }
+
+ Vector<Vector<String>> routes = getAllOption("route", 1, 4);
+ if(routes!=null) {
+ String routeopt = "";
+ for(Vector<String> route:routes){
+ String netmask = "255.255.255.255";
+ if(route.size() >= 3)
+ netmask = route.get(2);
+ String net = route.get(1);
+ try {
+ CIDRIP cidr = new CIDRIP(net, netmask);
+ routeopt+=cidr.toString() + " ";
+ } catch (ArrayIndexOutOfBoundsException aioob) {
+ throw new ConfigParseError("Could not parse netmask of route " + netmask);
+ } catch (NumberFormatException ne) {
+ throw new ConfigParseError("Could not parse netmask of route " + netmask);
+ }
+
+ }
+ np.mCustomRoutes=routeopt;
+ }
+
+ // Also recognize tls-auth [inline] direction ...
+ Vector<Vector<String>> tlsauthoptions = getAllOption("tls-auth", 1, 2);
+ if(tlsauthoptions!=null) {
+ for(Vector<String> tlsauth:tlsauthoptions) {
+ if(tlsauth!=null)
+ {
+ if(!tlsauth.get(1).equals("[inline]")) {
+ np.mTLSAuthFilename=tlsauth.get(1);
+ np.mUseTLSAuth=true;
+ }
+ if(tlsauth.size()==3)
+ np.mTLSAuthDirection=tlsauth.get(2);
+ }
+ }
+ }
+
+ Vector<String> direction = getOption("key-direction", 1, 1);
+ if(direction!=null)
+ np.mTLSAuthDirection=direction.get(1);
+
+
+ if(getAllOption("redirect-gateway", 0, 5) != null)
+ np.mUseDefaultRoute=true;
+
+ Vector<String> dev =getOption("dev",1,1);
+ Vector<String> devtype =getOption("dev-type",1,1);
+
+ if( (devtype !=null && devtype.get(1).equals("tun")) ||
+ (dev!=null && dev.get(1).startsWith("tun")) ||
+ (devtype ==null && dev == null) ) {
+ //everything okay
+ } else {
+ throw new ConfigParseError("Sorry. Only tun mode is supported. See the FAQ for more detail");
+ }
+
+
+
+ Vector<String> mode =getOption("mode",1,1);
+ if (mode != null){
+ if(!mode.get(1).equals("p2p"))
+ throw new ConfigParseError("Invalid mode for --mode specified, need p2p");
+ }
+
+ Vector<String> port = getOption("port", 1,1);
+ if(port!=null){
+ np.mServerPort = port.get(1);
+ }
+
+ Vector<String> proto = getOption("proto", 1,1);
+ 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);
+ if(dhcpoptions!=null) {
+ for(Vector<String> dhcpoption:dhcpoptions) {
+ String type=dhcpoption.get(1);
+ String arg = dhcpoption.get(2);
+ if(type.equals("DOMAIN")) {
+ np.mSearchDomain=dhcpoption.get(2);
+ } else if(type.equals("DNS")) {
+ np.mOverrideDNS=true;
+ if(np.mDNS1.equals(VpnProfile.DEFAULT_DNS1))
+ np.mDNS1=arg;
+ else
+ np.mDNS2=arg;
+ }
+ }
+ }
+
+ Vector<String> ifconfig = getOption("ifconfig", 2, 2);
+ if(ifconfig!=null) {
+ CIDRIP cidr = new CIDRIP(ifconfig.get(1), ifconfig.get(2));
+ np.mIPv4Address=cidr.toString();
+ }
+
+ if(getOption("remote-random-hostname", 0, 0)!=null)
+ np.mUseRandomHostname=true;
+
+ if(getOption("float", 0, 0)!=null)
+ np.mUseFloat=true;
+
+ if(getOption("comp-lzo", 0, 1)!=null)
+ np.mUseLzo=true;
+
+ Vector<String> cipher = getOption("cipher", 1, 1);
+ if(cipher!=null)
+ np.mCipher= cipher.get(1);
+
+ Vector<String> auth = getOption("auth", 1, 1);
+ if(auth!=null)
+ np.mAuth = auth.get(1);
+
+
+ Vector<String> ca = getOption("ca",1,1);
+ if(ca!=null){
+ np.mCaFilename = ca.get(1);
+ }
+
+ Vector<String> cert = getOption("cert",1,1);
+ if(cert!=null){
+ np.mClientCertFilename = cert.get(1);
+ np.mAuthenticationType = VpnProfile.TYPE_CERTIFICATES;
+ noauthtypeset=false;
+ }
+ Vector<String> key= getOption("key",1,1);
+ if(key!=null)
+ np.mClientKeyFilename=key.get(1);
+
+ Vector<String> pkcs12 = getOption("pkcs12",1,1);
+ if(pkcs12!=null) {
+ np.mPKCS12Filename = pkcs12.get(1);
+ np.mAuthenticationType = VpnProfile.TYPE_KEYSTORE;
+ noauthtypeset=false;
+ }
+
+
+ Vector<String> compatnames = getOption("compat-names",1,2);
+ Vector<String> nonameremapping = getOption("no-name-remapping",1,1);
+ Vector<String> tlsremote = getOption("tls-remote",1,1);
+ if(tlsremote!=null){
+ np.mRemoteCN = tlsremote.get(1);
+ np.mCheckRemoteCN=true;
+ np.mX509AuthType = VpnProfile.X509_VERIFY_TLSREMOTE;
+
+ if((compatnames!=null && compatnames.size() > 2) ||
+ (nonameremapping!=null))
+ np.mX509AuthType = VpnProfile.X509_VERIFY_TLSREMOTE_COMPAT_NOREMAPPING;
+ }
+
+ Vector<String> verifyx509name = getOption("verify-x509-name",1,2);
+ if(verifyx509name!=null){
+ np.mRemoteCN = verifyx509name.get(1);
+ np.mCheckRemoteCN=true;
+ if(verifyx509name.size()>2) {
+ if (verifyx509name.get(2).equals("name"))
+ np.mX509AuthType=VpnProfile.X509_VERIFY_TLSREMOTE_RDN;
+ else if (verifyx509name.get(2).equals("name-prefix"))
+ np.mX509AuthType=VpnProfile.X509_VERIFY_TLSREMOTE_RDN_PREFIX;
+ else
+ throw new ConfigParseError("Unknown parameter to x509-verify-name: " + verifyx509name.get(2) );
+ } else {
+ np.mX509AuthType = VpnProfile.X509_VERIFY_TLSREMOTE_DN;
+ }
+
+ }
+
+
+ Vector<String> verb = getOption("verb",1,1);
+ if(verb!=null){
+ np.mVerb=verb.get(1);
+ }
+
+
+ if(getOption("nobind", 0, 0) != null)
+ np.mNobind=true;
+
+ if(getOption("persist-tun", 0,0) != null)
+ np.mPersistTun=true;
+
+ Vector<String> connectretry = getOption("connect-retry", 1, 1);
+ if(connectretry!=null)
+ np.mConnectRetry =connectretry.get(1);
+
+ Vector<String> connectretrymax = getOption("connect-retry-max", 1, 1);
+ if(connectretrymax!=null)
+ np.mConnectRetryMax =connectretrymax.get(1);
+
+ Vector<Vector<String>> remotetls = getAllOption("remote-cert-tls", 1, 1);
+ if(remotetls!=null)
+ if(remotetls.get(0).get(1).equals("server"))
+ np.mExpectTLSCert=true;
+ else
+ options.put("remotetls",remotetls);
+
+ Vector<String> authuser = getOption("auth-user-pass",0,1);
+ if(authuser !=null){
+ if(noauthtypeset) {
+ np.mAuthenticationType=VpnProfile.TYPE_USERPASS;
+ } else if(np.mAuthenticationType==VpnProfile.TYPE_CERTIFICATES) {
+ np.mAuthenticationType=VpnProfile.TYPE_USERPASS_CERTIFICATES;
+ } else if(np.mAuthenticationType==VpnProfile.TYPE_KEYSTORE) {
+ np.mAuthenticationType=VpnProfile.TYPE_USERPASS_KEYSTORE;
+ }
+ if(authuser.size()>1) {
+ // Set option value to password get to get cance to embed later.
+ np.mUsername=null;
+ np.mPassword=authuser.get(1);
+ useEmbbedUserAuth(np,authuser.get(1));
+ }
+ }
+
+ // Parse OpenVPN Access Server extra
+ Vector<String> friendlyname = meta.get("FRIENDLY_NAME");
+ if(friendlyname !=null && friendlyname.size() > 1)
+ np.mName=friendlyname.get(1);
+
+
+ Vector<String> ocusername = meta.get("USERNAME");
+ if(ocusername !=null && ocusername.size() > 1)
+ np.mUsername=ocusername.get(1);
+
+ // Check the other options
+ if(remotes !=null && remotes.size()>1 && extraRemotesAsCustom) {
+ // first is already added
+ remotes.remove(0);
+ np.mCustomConfigOptions += getOptionStrings(remotes);
+ np.mUseCustomConfig=true;
+
+ }
+ checkIgnoreAndInvalidOptions(np);
+ fixup(np);
+
+ return np;
+ }
+
+ 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("tcp6") ||
+ proto.endsWith("tcp6-client"))
+ isudp =false;
+ else
+ throw new ConfigParseError("Unsupported option to --proto " + proto);
+ return isudp;
+ }
+
+ static public void useEmbbedUserAuth(VpnProfile np,String inlinedata)
+ {
+ String data = inlinedata.replace(VpnProfile.INLINE_TAG, "");
+ String[] parts = data.split("\n");
+ if(parts.length >= 2) {
+ np.mUsername=parts[0];
+ np.mPassword=parts[1];
+ }
+ }
+
+ private void checkIgnoreAndInvalidOptions(VpnProfile np) throws ConfigParseError {
+ for(String option:unsupportedOptions)
+ if(options.containsKey(option))
+ throw new ConfigParseError(String.format("Unsupported Option %s encountered in config file. Aborting",option));
+
+ for(String option:ignoreOptions)
+ // removing an item which is not in the map is no error
+ options.remove(option);
+
+ if(options.size()> 0) {
+ np.mCustomConfigOptions += "# These Options were found in the config file do not map to config settings:\n";
+
+ for(Vector<Vector<String>> option:options.values()) {
+ np.mCustomConfigOptions += getOptionStrings(option);
+
+ }
+ np.mUseCustomConfig=true;
+
+ }
+ }
+
+ private String getOptionStrings( Vector<Vector<String>> option) {
+ String custom="";
+ for(Vector<String> optionsline: option) {
+ for (String arg : optionsline)
+ custom+= VpnProfile.openVpnEscape(arg) + " ";
+ custom+="\n";
+ }
+ return custom;
+ }
+
+
+ private void fixup(VpnProfile np) {
+ if(np.mRemoteCN.equals(np.mServerName)) {
+ np.mRemoteCN="";
+ }
+ }
+
+ private Vector<String> getOption(String option, int minarg, int maxarg) throws ConfigParseError {
+ Vector<Vector<String>> alloptions = getAllOption(option, minarg, maxarg);
+ if(alloptions==null)
+ return null;
+ else
+ return alloptions.lastElement();
+ }
+
+
+ private Vector<Vector<String>> getAllOption(String option, int minarg, int maxarg) throws ConfigParseError {
+ Vector<Vector<String>> args = options.get(option);
+ if(args==null)
+ return null;
+
+ for(Vector<String> optionline:args)
+
+ if(optionline.size()< (minarg+1) || optionline.size() > maxarg+1) {
+ String err = String.format(Locale.getDefault(),"Option %s has %d parameters, expected between %d and %d",
+ option,optionline.size()-1,minarg,maxarg );
+ throw new ConfigParseError(err);
+ }
+ options.remove(option);
+ return args;
+ }
+
+}
+
+
+
+
diff --git a/src/de/blinkt/openvpn/core/NetworkSateReceiver.java b/src/de/blinkt/openvpn/core/NetworkSateReceiver.java
new file mode 100644
index 00000000..aa828495
--- /dev/null
+++ b/src/de/blinkt/openvpn/core/NetworkSateReceiver.java
@@ -0,0 +1,90 @@
+package de.blinkt.openvpn.core;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.State;
+import android.preference.PreferenceManager;
+import de.blinkt.openvpn.R;
+
+public class NetworkSateReceiver extends BroadcastReceiver {
+ private int lastNetwork=-1;
+ private OpenVPNMangement mManangement;
+
+ private String lastStateMsg=null;
+
+ public NetworkSateReceiver(OpenVPNMangement magnagement) {
+ super();
+ mManangement = magnagement;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ NetworkInfo networkInfo = getCurrentNetworkInfo(context);
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ boolean sendusr1 = prefs.getBoolean("netchangereconnect", true);
+
+ String netstatestring;
+ if(networkInfo==null)
+ netstatestring = "not connected";
+ else {
+ String subtype = networkInfo.getSubtypeName();
+ if(subtype==null)
+ subtype = "";
+ String extrainfo = networkInfo.getExtraInfo();
+ if(extrainfo==null)
+ extrainfo="";
+
+ /*
+ if(networkInfo.getType()==android.net.ConnectivityManager.TYPE_WIFI) {
+ WifiManager wifiMgr = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ WifiInfo wifiinfo = wifiMgr.getConnectionInfo();
+ extrainfo+=wifiinfo.getBSSID();
+
+ subtype += wifiinfo.getNetworkId();
+ }*/
+
+
+
+ netstatestring = String.format("%2$s %4$s to %1$s %3$s",networkInfo.getTypeName(),
+ networkInfo.getDetailedState(),extrainfo,subtype );
+ }
+
+
+
+ if(networkInfo!=null && networkInfo.getState() == State.CONNECTED) {
+ int newnet = networkInfo.getType();
+
+ if(sendusr1 && lastNetwork!=newnet) {
+ if (lastNetwork==-1)
+ mManangement.resume();
+ else
+ mManangement.reconnect();
+ }
+
+ lastNetwork = newnet;
+ } else if (networkInfo==null) {
+ // Not connected, stop openvpn, set last connected network to no network
+ lastNetwork=-1;
+ if(sendusr1)
+ mManangement.pause();
+ }
+
+ if(!netstatestring.equals(lastStateMsg))
+ OpenVPN.logInfo(R.string.netstatus, netstatestring);
+ lastStateMsg=netstatestring;
+
+ }
+
+ private NetworkInfo getCurrentNetworkInfo(Context context) {
+ ConnectivityManager conn = (ConnectivityManager)
+ context.getSystemService(Context.CONNECTIVITY_SERVICE);
+
+ NetworkInfo networkInfo = conn.getActiveNetworkInfo();
+ return networkInfo;
+ }
+
+}
diff --git a/src/de/blinkt/openvpn/core/OpenVPN.java b/src/de/blinkt/openvpn/core/OpenVPN.java
new file mode 100644
index 00000000..aba3ef0c
--- /dev/null
+++ b/src/de/blinkt/openvpn/core/OpenVPN.java
@@ -0,0 +1,427 @@
+package de.blinkt.openvpn.core;
+
+import java.io.ByteArrayInputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.Locale;
+import java.util.Vector;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.Signature;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import de.blinkt.openvpn.R;
+
+public class OpenVPN {
+
+
+ public static LinkedList<LogItem> logbuffer;
+
+ private static Vector<LogListener> logListener;
+ private static Vector<StateListener> stateListener;
+ private static Vector<ByteCountListener> byteCountListener;
+
+ private static String[] mBconfig;
+
+ private static String mLaststatemsg="";
+
+ private static String mLaststate = "NOPROCESS";
+
+ private static int mLastStateresid=R.string.state_noprocess;
+
+ private static long mlastByteCount[]={0,0,0,0};
+
+
+
+ public enum ConnectionStatus {
+ LEVEL_NONETWORK (3),
+ LEVEL_NOTCONNECTED (4),
+ LEVEL_AUTH_FAILED ( 5),
+ LEVEL_CONNECTING_SERVER_REPLIED ( 1),
+ LEVEL_CONNECTING_NO_SERVER_REPLY_YET (2),
+ LEVEL_CONNECTED (0), UNKNOWN_LEVEL(-1);
+
+ public final int level;
+
+ ConnectionStatus(int level){
+ this.level = level;
+ }
+ }
+
+ public static final byte[] officalkey = {-58, -42, -44, -106, 90, -88, -87, -88, -52, -124, 84, 117, 66, 79, -112, -111, -46, 86, -37, 109};
+ public static final byte[] officaldebugkey = {-99, -69, 45, 71, 114, -116, 82, 66, -99, -122, 50, -70, -56, -111, 98, -35, -65, 105, 82, 43};
+ public static final byte[] amazonkey = {-116, -115, -118, -89, -116, -112, 120, 55, 79, -8, -119, -23, 106, -114, -85, -56, -4, 105, 26, -57};
+
+ private static ConnectionStatus mLastLevel=ConnectionStatus.LEVEL_NOTCONNECTED;
+
+ static {
+ logbuffer = new LinkedList<LogItem>();
+ logListener = new Vector<OpenVPN.LogListener>();
+ stateListener = new Vector<OpenVPN.StateListener>();
+ byteCountListener = new Vector<OpenVPN.ByteCountListener>();
+ logInformation();
+ }
+
+
+ public static class LogItem implements Parcelable {
+ public static final int ERROR = 1;
+ public static final int INFO = 2;
+ public static final int VERBOSE = 3;
+
+ private Object [] mArgs = null;
+ private String mMessage = null;
+ private int mRessourceId;
+ // Default log priority
+ int mLevel = INFO;
+ private long logtime = System.currentTimeMillis();
+
+ public LogItem(int ressourceId, Object[] args) {
+ mRessourceId = ressourceId;
+ mArgs = args;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeArray(mArgs);
+ dest.writeString(mMessage);
+ dest.writeInt(mRessourceId);
+ dest.writeInt(mLevel);
+ dest.writeLong(logtime);
+ }
+
+ public LogItem(Parcel in) {
+ mArgs = in.readArray(Object.class.getClassLoader());
+ mMessage = in.readString();
+ mRessourceId = in.readInt();
+ mLevel = in.readInt();
+ logtime = in.readLong();
+ }
+
+ public static final Parcelable.Creator<LogItem> CREATOR
+ = new Parcelable.Creator<LogItem>() {
+ public LogItem createFromParcel(Parcel in) {
+ return new LogItem(in);
+ }
+
+ public LogItem[] newArray(int size) {
+ return new LogItem[size];
+ }
+ };
+
+ public LogItem(int loglevel,int ressourceId, Object[] args) {
+ mRessourceId = ressourceId;
+ mArgs = args;
+ mLevel = loglevel;
+ }
+
+
+ public LogItem(String message) {
+ mMessage = message;
+ }
+
+ public LogItem(int loglevel, String msg) {
+ mLevel = loglevel;
+ mMessage = msg;
+ }
+
+
+ public LogItem(int loglevel, int ressourceId) {
+ mRessourceId =ressourceId;
+ mLevel = loglevel;
+ }
+
+ public String getString(Context c) {
+ if(mMessage !=null) {
+ return mMessage;
+ } else {
+ if(c!=null) {
+ if(mRessourceId==R.string.mobile_info)
+ return getMobileInfoString(c);
+ if(mArgs == null)
+ return c.getString(mRessourceId);
+ else
+ return c.getString(mRessourceId,mArgs);
+ } else {
+ String str = String.format(Locale.ENGLISH,"Log (no context) resid %d", mRessourceId);
+ if(mArgs !=null)
+ for(Object o:mArgs)
+ str += "|" + o.toString();
+
+ return str;
+ }
+ }
+ }
+
+ // The lint is wrong here
+ @SuppressLint("StringFormatMatches")
+ private String getMobileInfoString(Context c) {
+ c.getPackageManager();
+ String apksign="error getting package signature";
+
+ String version="error getting version";
+ try {
+ Signature raw = c.getPackageManager().getPackageInfo(c.getPackageName(), PackageManager.GET_SIGNATURES).signatures[0];
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(raw.toByteArray()));
+ MessageDigest md = MessageDigest.getInstance("SHA-1");
+ byte[] der = cert.getEncoded();
+ md.update(der);
+ byte[] digest = md.digest();
+
+ if (Arrays.equals(digest, officalkey))
+ apksign = c.getString(R.string.official_build);
+ else if (Arrays.equals(digest, officaldebugkey))
+ apksign = c.getString(R.string.debug_build);
+ else if (Arrays.equals(digest, amazonkey))
+ apksign = "amazon version";
+ else
+ apksign = c.getString(R.string.built_by,cert.getSubjectX500Principal().getName());
+
+ PackageInfo packageinfo = c.getPackageManager().getPackageInfo(c.getPackageName(), 0);
+ version = packageinfo.versionName;
+
+ } catch (NameNotFoundException e) {
+ } catch (CertificateException e) {
+ } catch (NoSuchAlgorithmException e) {
+ }
+
+ Object[] argsext = Arrays.copyOf(mArgs, mArgs.length+2);
+ argsext[argsext.length-1]=apksign;
+ argsext[argsext.length-2]=version;
+
+ return c.getString(R.string.mobile_info_extended, argsext);
+
+ }
+
+ public long getLogtime() {
+ return logtime;
+ }
+
+
+ }
+
+ private static final int MAXLOGENTRIES = 500;
+
+ public static final String MANAGMENT_PREFIX = "M:";
+
+
+ public interface LogListener {
+ void newLog(LogItem logItem);
+ }
+
+ public interface StateListener {
+ void updateState(String state, String logmessage, int localizedResId, ConnectionStatus level);
+ }
+
+ public interface ByteCountListener {
+ void updateByteCount(long in, long out, long diffin, long diffout);
+ }
+
+ public synchronized static void logMessage(int level,String prefix, String message)
+ {
+ newlogItem(new LogItem(prefix + message));
+
+ }
+
+ public synchronized static void clearLog() {
+ logbuffer.clear();
+ logInformation();
+ }
+
+ private static void logInformation() {
+
+
+ logInfo(R.string.mobile_info,Build.MODEL, Build.BOARD,Build.BRAND,Build.VERSION.SDK_INT);
+ }
+
+ public synchronized static void addLogListener(LogListener ll){
+ logListener.add(ll);
+ }
+
+ public synchronized static void removeLogListener(LogListener ll) {
+ logListener.remove(ll);
+ }
+
+ public synchronized static void addByteCountListener(ByteCountListener bcl) {
+ bcl.updateByteCount(mlastByteCount[0], mlastByteCount[1], mlastByteCount[2], mlastByteCount[3]);
+ byteCountListener.add(bcl);
+ }
+
+ public synchronized static void removeByteCountListener(ByteCountListener bcl) {
+ byteCountListener.remove(bcl);
+ }
+
+
+ public synchronized static void addStateListener(StateListener sl){
+ if(!stateListener.contains(sl)){
+ stateListener.add(sl);
+ if(mLaststate!=null)
+ sl.updateState(mLaststate, mLaststatemsg, mLastStateresid, mLastLevel);
+ }
+ }
+
+ private static int getLocalizedState(String state){
+ if (state.equals("CONNECTING"))
+ return R.string.state_connecting;
+ else if (state.equals("WAIT"))
+ return R.string.state_wait;
+ else if (state.equals("AUTH"))
+ return R.string.state_auth;
+ else if (state.equals("GET_CONFIG"))
+ return R.string.state_get_config;
+ else if (state.equals("ASSIGN_IP"))
+ return R.string.state_assign_ip;
+ else if (state.equals("ADD_ROUTES"))
+ return R.string.state_add_routes;
+ else if (state.equals("CONNECTED"))
+ return R.string.state_connected;
+ else if (state.equals("DISCONNECTED"))
+ return R.string.state_disconnected;
+ else if (state.equals("RECONNECTING"))
+ return R.string.state_reconnecting;
+ else if (state.equals("EXITING"))
+ return R.string.state_exiting;
+ else if (state.equals("RESOLVE"))
+ return R.string.state_resolve;
+ else if (state.equals("TCP_CONNECT"))
+ return R.string.state_tcp_connect;
+ else
+ return R.string.unknown_state;
+
+ }
+
+ private static ConnectionStatus getLevel(String state){
+ String[] noreplyet = {"CONNECTING","WAIT", "RECONNECTING", "RESOLVE", "TCP_CONNECT"};
+ String[] reply = {"AUTH","GET_CONFIG","ASSIGN_IP","ADD_ROUTES"};
+ String[] connected = {"CONNECTED"};
+ String[] notconnected = {"DISCONNECTED", "EXITING"};
+
+ for(String x:noreplyet)
+ if(state.equals(x))
+ return ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET;
+
+ for(String x:reply)
+ if(state.equals(x))
+ return ConnectionStatus.LEVEL_CONNECTING_SERVER_REPLIED;
+
+ for(String x:connected)
+ if(state.equals(x))
+ return ConnectionStatus.LEVEL_CONNECTED;
+
+ for(String x:notconnected)
+ if(state.equals(x))
+ return ConnectionStatus.LEVEL_NOTCONNECTED;
+
+ return ConnectionStatus.UNKNOWN_LEVEL;
+
+ }
+
+
+
+
+ public synchronized static void removeStateListener(StateListener sl) {
+ stateListener.remove(sl);
+ }
+
+
+ synchronized public static LogItem[] getlogbuffer() {
+
+ // The stoned way of java to return an array from a vector
+ // brought to you by eclipse auto complete
+ return (LogItem[]) logbuffer.toArray(new LogItem[logbuffer.size()]);
+
+ }
+ public static void logBuilderConfig(String[] bconfig) {
+ mBconfig = bconfig;
+ }
+ public static void triggerLogBuilderConfig() {
+ if(mBconfig==null) {
+ logMessage(0, "", "No active interface");
+ } else {
+ for (String item : mBconfig) {
+ logMessage(0, "", item);
+ }
+ }
+
+ }
+
+ public static void updateStateString (String state, String msg) {
+ int rid = getLocalizedState(state);
+ ConnectionStatus level = getLevel(state);
+ updateStateString(state, msg, rid, level);
+ }
+
+ public synchronized static void updateStateString(String state, String msg, int resid, ConnectionStatus level) {
+ mLaststate= state;
+ mLaststatemsg = msg;
+ mLastStateresid = resid;
+ mLastLevel = level;
+
+ for (StateListener sl : stateListener) {
+ sl.updateState(state,msg,resid,level);
+ }
+ }
+
+ public static void logInfo(String message) {
+ newlogItem(new LogItem(LogItem.INFO, message));
+ }
+
+ public static void logInfo(int ressourceId, Object... args) {
+ newlogItem(new LogItem(LogItem.INFO, ressourceId, args));
+ }
+
+ private synchronized static void newlogItem(LogItem logItem) {
+ logbuffer.addLast(logItem);
+ if(logbuffer.size()>MAXLOGENTRIES)
+ logbuffer.removeFirst();
+
+ for (LogListener ll : logListener) {
+ ll.newLog(logItem);
+ }
+ }
+
+ public static void logError(String msg) {
+ newlogItem(new LogItem(LogItem.ERROR, msg));
+
+ }
+
+ public static void logError(int ressourceId) {
+ newlogItem(new LogItem(LogItem.ERROR, ressourceId));
+ }
+ public static void logError(int ressourceId, Object... args) {
+ newlogItem(new LogItem(LogItem.ERROR, ressourceId,args));
+ }
+
+ public static synchronized void updateByteCount(long in, long out) {
+ long lastIn = mlastByteCount[0];
+ long lastOut = mlastByteCount[1];
+ long diffin = mlastByteCount[2] = in - lastIn;
+ long diffout = mlastByteCount[3] = out - lastOut;
+
+
+
+ mlastByteCount = new long[] {in,out,diffin,diffout};
+ for(ByteCountListener bcl:byteCountListener){
+ bcl.updateByteCount(in, out, diffin,diffout);
+ }
+ }
+
+
+
+}
diff --git a/src/de/blinkt/openvpn/core/OpenVPNMangement.java b/src/de/blinkt/openvpn/core/OpenVPNMangement.java
new file mode 100644
index 00000000..a1334ac2
--- /dev/null
+++ b/src/de/blinkt/openvpn/core/OpenVPNMangement.java
@@ -0,0 +1,14 @@
+package de.blinkt.openvpn.core;
+
+public interface OpenVPNMangement {
+ int mBytecountinterval=2;
+
+ void reconnect();
+
+ void pause();
+
+ void resume();
+
+ boolean stopVPN();
+
+}
diff --git a/src/de/blinkt/openvpn/core/OpenVPNThread.java b/src/de/blinkt/openvpn/core/OpenVPNThread.java
new file mode 100644
index 00000000..9d6d8e77
--- /dev/null
+++ b/src/de/blinkt/openvpn/core/OpenVPNThread.java
@@ -0,0 +1,136 @@
+package de.blinkt.openvpn.core;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.Locale;
+
+import android.util.Log;
+import de.blinkt.openvpn.R;
+import de.blinkt.openvpn.VpnProfile;
+import de.blinkt.openvpn.core.OpenVPN.ConnectionStatus;
+import de.blinkt.openvpn.core.OpenVPN.LogItem;
+
+public class OpenVPNThread implements Runnable {
+ private static final String DUMP_PATH_STRING = "Dump path: ";
+ private static final String TAG = "OpenVPN";
+ private String[] mArgv;
+ private Process mProcess;
+ private String mNativeDir;
+ private OpenVpnService mService;
+ private String mDumpPath;
+
+ public OpenVPNThread(OpenVpnService service,String[] argv, String nativelibdir)
+ {
+ mArgv = argv;
+ mNativeDir = nativelibdir;
+ mService = service;
+ }
+
+ public void stopProcess() {
+ mProcess.destroy();
+ }
+
+
+
+ @Override
+ public void run() {
+ try {
+ Log.i(TAG, "Starting openvpn");
+ startOpenVPNThreadArgs(mArgv);
+ Log.i(TAG, "Giving up");
+ } catch (Exception e) {
+ e.printStackTrace();
+ Log.e(TAG, "OpenVPNThread Got " + e.toString());
+ } finally {
+ int exitvalue = 0;
+ try {
+ exitvalue = mProcess.waitFor();
+ } catch ( IllegalThreadStateException ite) {
+ OpenVPN.logError("Illegal Thread state: " + ite.getLocalizedMessage());
+ } catch (InterruptedException ie) {
+ OpenVPN.logError("InterruptedException: " + ie.getLocalizedMessage());
+ }
+ if( exitvalue != 0)
+ OpenVPN.logError("Process exited with exit value " + exitvalue);
+
+ OpenVPN.updateStateString("NOPROCESS","No process running.", R.string.state_noprocess,ConnectionStatus.LEVEL_NOTCONNECTED);
+ if(mDumpPath!=null) {
+ try {
+ BufferedWriter logout = new BufferedWriter(new FileWriter(mDumpPath + ".log"));
+ SimpleDateFormat timeformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss",Locale.GERMAN);
+ for(LogItem li :OpenVPN.getlogbuffer()){
+ String time = timeformat.format(new Date(li.getLogtime()));
+ logout.write(time +" " + li.getString(mService) + "\n");
+ }
+ logout.close();
+ OpenVPN.logError(R.string.minidump_generated);
+ } catch (IOException e) {
+ OpenVPN.logError("Writing minidump log: " +e.getLocalizedMessage());
+ }
+ }
+
+ mService.processDied();
+ Log.i(TAG, "Exiting");
+ }
+ }
+
+ private void startOpenVPNThreadArgs(String[] argv) {
+ LinkedList<String> argvlist = new LinkedList<String>();
+
+ for(String arg:argv)
+ argvlist.add(arg);
+
+ ProcessBuilder pb = new ProcessBuilder(argvlist);
+ // Hack O rama
+
+ // Hack until I find a good way to get the real library path
+ String applibpath = argv[0].replace("/cache/" + VpnProfile.MINIVPN , "/lib");
+
+ String lbpath = pb.environment().get("LD_LIBRARY_PATH");
+ if(lbpath==null)
+ lbpath = applibpath;
+ else
+ lbpath = lbpath + ":" + applibpath;
+
+ if (!applibpath.equals(mNativeDir)) {
+ lbpath = lbpath + ":" + mNativeDir;
+ }
+
+ pb.environment().put("LD_LIBRARY_PATH", lbpath);
+ pb.redirectErrorStream(true);
+ try {
+ mProcess = pb.start();
+ // Close the output, since we don't need it
+ mProcess.getOutputStream().close();
+ InputStream in = mProcess.getInputStream();
+ BufferedReader br = new BufferedReader(new InputStreamReader(in));
+
+ while(true) {
+ String logline = br.readLine();
+ if(logline==null)
+ return;
+
+ if (logline.startsWith(DUMP_PATH_STRING))
+ mDumpPath = logline.substring(DUMP_PATH_STRING.length());
+
+
+ OpenVPN.logMessage(0, "P:", logline);
+ }
+
+
+ } catch (IOException e) {
+ OpenVPN.logMessage(0, "", "Error reading from output of OpenVPN process"+ e.getLocalizedMessage());
+ e.printStackTrace();
+ stopProcess();
+ }
+
+
+ }
+}
diff --git a/src/de/blinkt/openvpn/core/OpenVpnManagementThread.java b/src/de/blinkt/openvpn/core/OpenVpnManagementThread.java
new file mode 100644
index 00000000..a44f744e
--- /dev/null
+++ b/src/de/blinkt/openvpn/core/OpenVpnManagementThread.java
@@ -0,0 +1,482 @@
+package de.blinkt.openvpn.core;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.LinkedList;
+import java.util.Locale;
+import java.util.Vector;
+
+import android.content.SharedPreferences;
+import android.net.LocalServerSocket;
+import android.net.LocalSocket;
+import android.os.ParcelFileDescriptor;
+import android.preference.PreferenceManager;
+import android.util.Log;
+import de.blinkt.openvpn.R;
+import de.blinkt.openvpn.VpnProfile;
+import de.blinkt.openvpn.core.OpenVPN.ConnectionStatus;
+
+public class OpenVpnManagementThread implements Runnable, OpenVPNMangement {
+
+ private static final String TAG = "openvpn";
+ private LocalSocket mSocket;
+ private VpnProfile mProfile;
+ private OpenVpnService mOpenVPNService;
+ private LinkedList<FileDescriptor> mFDList=new LinkedList<FileDescriptor>();
+ private LocalServerSocket mServerSocket;
+ private boolean mReleaseHold=true;
+ private boolean mWaitingForRelease=false;
+ private long mLastHoldRelease=0;
+
+ private static Vector<OpenVpnManagementThread> active=new Vector<OpenVpnManagementThread>();
+
+ static private native void jniclose(int fdint);
+
+ public OpenVpnManagementThread(VpnProfile profile, LocalServerSocket mgmtsocket, OpenVpnService openVpnService) {
+ mProfile = profile;
+ mServerSocket = mgmtsocket;
+ mOpenVPNService = openVpnService;
+
+
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(openVpnService);
+ boolean managemeNetworkState = prefs.getBoolean("netchangereconnect", true);
+ if(managemeNetworkState)
+ mReleaseHold=false;
+
+ }
+
+ static {
+ System.loadLibrary("opvpnutil");
+ }
+
+ public void managmentCommand(String cmd) {
+ if(mSocket!=null) {
+ try {
+ mSocket.getOutputStream().write(cmd.getBytes());
+ mSocket.getOutputStream().flush();
+ } catch (IOException e) {
+ // Ignore socket stack traces
+ }
+ }
+ }
+
+
+ @Override
+ public void run() {
+ Log.i(TAG, "Managment Socket Thread started");
+ byte [] buffer =new byte[2048];
+ // mSocket.setSoTimeout(5); // Setting a timeout cannot be that bad
+
+ String pendingInput="";
+ active.add(this);
+
+ try {
+ // Wait for a client to connect
+ mSocket= mServerSocket.accept();
+ InputStream instream = mSocket.getInputStream();
+
+ while(true) {
+ int numbytesread = instream.read(buffer);
+ if(numbytesread==-1)
+ return;
+
+ FileDescriptor[] fds = null;
+ try {
+ fds = mSocket.getAncillaryFileDescriptors();
+ } catch (IOException e) {
+ OpenVPN.logMessage(0, "", "Error reading fds from socket" + e.getLocalizedMessage());
+ e.printStackTrace();
+ }
+ if(fds!=null){
+
+ for (FileDescriptor fd : fds) {
+
+ mFDList.add(fd);
+ }
+ }
+
+ String input = new String(buffer,0,numbytesread,"UTF-8");
+
+ pendingInput += input;
+
+ pendingInput=processInput(pendingInput);
+
+
+
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ active.remove(this);
+ }
+
+ //! Hack O Rama 2000!
+ private void protectFileDescriptor(FileDescriptor fd) {
+ Exception exp=null;
+ try {
+ Method getInt = FileDescriptor.class.getDeclaredMethod("getInt$");
+ int fdint = (Integer) getInt.invoke(fd);
+
+ // You can even get more evil by parsing toString() and extract the int from that :)
+
+ mOpenVPNService.protect(fdint);
+
+ //ParcelFileDescriptor pfd = ParcelFileDescriptor.fromFd(fdint);
+ //pfd.close();
+ jniclose(fdint);
+ return;
+ } catch (NoSuchMethodException e) {
+ exp =e;
+ } catch (IllegalArgumentException e) {
+ exp =e;
+ } catch (IllegalAccessException e) {
+ exp =e;
+ } catch (InvocationTargetException e) {
+ exp =e;
+ } catch (NullPointerException e) {
+ exp =e;
+ }
+ if(exp!=null) {
+ exp.printStackTrace();
+ Log.d("Openvpn", "Failed to retrieve fd from socket: " + fd);
+ OpenVPN.logMessage(0, "", "Failed to retrieve fd from socket: " + exp.getLocalizedMessage());
+ }
+ }
+
+ private String processInput(String pendingInput) {
+
+
+ while(pendingInput.contains("\n")) {
+ String[] tokens = pendingInput.split("\\r?\\n", 2);
+ processCommand(tokens[0]);
+ if(tokens.length == 1)
+ // No second part, newline was at the end
+ pendingInput="";
+ else
+ pendingInput=tokens[1];
+ }
+ return pendingInput;
+ }
+
+
+ private void processCommand(String command) {
+ if (command.startsWith(">") && command.contains(":")) {
+ String[] parts = command.split(":",2);
+ String cmd = parts[0].substring(1);
+ String argument = parts[1];
+
+
+ if(cmd.equals("INFO")) {
+ // Ignore greeting from mgmt
+ //logStatusMessage(command);
+ }else if (cmd.equals("PASSWORD")) {
+ processPWCommand(argument);
+ } else if (cmd.equals("HOLD")) {
+ handleHold();
+ } else if (cmd.equals("NEED-OK")) {
+ processNeedCommand(argument);
+ } else if (cmd.equals("BYTECOUNT")){
+ processByteCount(argument);
+ } else if (cmd.equals("STATE")) {
+ processState(argument);
+ } else if (cmd.equals("PROXY")) {
+ processProxyCMD(argument);
+ } else if (cmd.equals("LOG")) {
+ String[] args = argument.split(",",3);
+ // 0 unix time stamp
+ // 1 log level N,I,E etc.
+ // 2 log message
+ OpenVPN.logMessage(0, "", args[2]);
+ } else if (cmd.equals("RSA_SIGN")) {
+ processSignCommand(argument);
+ } else {
+ OpenVPN.logMessage(0, "MGMT:", "Got unrecognized command" + command);
+ Log.i(TAG, "Got unrecognized command" + command);
+ }
+ } else if (command.startsWith("SUCCESS:")) {
+ // ignore
+ } else {
+ Log.i(TAG, "Got unrecognized line from managment" + command);
+ OpenVPN.logMessage(0, "MGMT:", "Got unrecognized line from management:" + command);
+ }
+ }
+ private void handleHold() {
+ if(mReleaseHold) {
+ releaseHoldCmd();
+ } else {
+ mWaitingForRelease=true;
+ OpenVPN.updateStateString("NONETWORK", "",R.string.state_nonetwork,ConnectionStatus.LEVEL_NONETWORK);
+ }
+ }
+ private void releaseHoldCmd() {
+ if ((System.currentTimeMillis()- mLastHoldRelease) < 5000) {
+ try {
+ Thread.sleep(3000);
+ } catch (InterruptedException e) {}
+
+ }
+ mWaitingForRelease=false;
+ mLastHoldRelease = System.currentTimeMillis();
+ managmentCommand("hold release\n");
+ managmentCommand("bytecount " + mBytecountinterval + "\n");
+ managmentCommand("state on\n");
+ }
+
+ public void releaseHold() {
+ mReleaseHold=true;
+ if(mWaitingForRelease)
+ releaseHoldCmd();
+
+ }
+
+ private void processProxyCMD(String argument) {
+ String[] args = argument.split(",",3);
+ SocketAddress proxyaddr = ProxyDetection.detectProxy(mProfile);
+
+
+ if(args.length >= 2) {
+ String proto = args[1];
+ if(proto.equals("UDP")) {
+ proxyaddr=null;
+ }
+ }
+
+ if(proxyaddr instanceof InetSocketAddress ){
+ InetSocketAddress isa = (InetSocketAddress) proxyaddr;
+
+ OpenVPN.logInfo(R.string.using_proxy, isa.getHostName(),isa.getPort());
+
+ String proxycmd = String.format(Locale.ENGLISH,"proxy HTTP %s %d\n", isa.getHostName(),isa.getPort());
+ managmentCommand(proxycmd);
+ } else {
+ managmentCommand("proxy NONE\n");
+ }
+
+ }
+ private void processState(String argument) {
+ String[] args = argument.split(",",3);
+ String currentstate = args[1];
+ if(args[2].equals(",,"))
+ OpenVPN.updateStateString(currentstate,"");
+ else
+ OpenVPN.updateStateString(currentstate,args[2]);
+ }
+
+
+ private void processByteCount(String argument) {
+ // >BYTECOUNT:{BYTES_IN},{BYTES_OUT}
+ int comma = argument.indexOf(',');
+ long in = Long.parseLong(argument.substring(0, comma));
+ long out = Long.parseLong(argument.substring(comma+1));
+
+ OpenVPN.updateByteCount(in,out);
+
+ }
+
+
+
+ private void processNeedCommand(String argument) {
+ int p1 =argument.indexOf('\'');
+ int p2 = argument.indexOf('\'',p1+1);
+
+ String needed = argument.substring(p1+1, p2);
+ String extra = argument.split(":",2)[1];
+
+ String status = "ok";
+
+
+ if (needed.equals("PROTECTFD")) {
+ FileDescriptor fdtoprotect = mFDList.pollFirst();
+ protectFileDescriptor(fdtoprotect);
+ } else if (needed.equals("DNSSERVER")) {
+ mOpenVPNService.addDNS(extra);
+ }else if (needed.equals("DNSDOMAIN")){
+ mOpenVPNService.setDomain(extra);
+ } else if (needed.equals("ROUTE")) {
+ String[] routeparts = extra.split(" ");
+ mOpenVPNService.addRoute(routeparts[0], routeparts[1]);
+ } else if (needed.equals("ROUTE6")) {
+ mOpenVPNService.addRoutev6(extra);
+ } else if (needed.equals("IFCONFIG")) {
+ String[] ifconfigparts = extra.split(" ");
+ int mtu = Integer.parseInt(ifconfigparts[2]);
+ mOpenVPNService.setLocalIP(ifconfigparts[0], ifconfigparts[1],mtu,ifconfigparts[3]);
+ } else if (needed.equals("IFCONFIG6")) {
+ mOpenVPNService.setLocalIPv6(extra);
+
+ } else if (needed.equals("OPENTUN")) {
+ if(sendTunFD(needed,extra))
+ return;
+ else
+ status="cancel";
+ // This not nice or anything but setFileDescriptors accepts only FilDescriptor class :(
+
+ } else {
+ Log.e(TAG,"Unkown needok command " + argument);
+ return;
+ }
+
+ String cmd = String.format("needok '%s' %s\n", needed, status);
+ managmentCommand(cmd);
+ }
+
+ private boolean sendTunFD (String needed, String extra) {
+ Exception exp = null;
+ if(!extra.equals("tun")) {
+ // We only support tun
+ String errmsg = String.format("Devicetype %s requested, but only tun is possible with the Android API, sorry!",extra);
+ OpenVPN.logMessage(0, "", errmsg );
+
+ return false;
+ }
+ ParcelFileDescriptor pfd = mOpenVPNService.openTun();
+ if(pfd==null)
+ return false;
+
+ Method setInt;
+ int fdint = pfd.getFd();
+ try {
+ setInt = FileDescriptor.class.getDeclaredMethod("setInt$",int.class);
+ FileDescriptor fdtosend = new FileDescriptor();
+
+ setInt.invoke(fdtosend,fdint);
+
+ FileDescriptor[] fds = {fdtosend};
+ mSocket.setFileDescriptorsForSend(fds);
+
+ Log.d("Openvpn", "Sending FD tosocket: " + fdtosend + " " + fdint + " " + pfd);
+ // Trigger a send so we can close the fd on our side of the channel
+ // The API documentation fails to mention that it will not reset the file descriptor to
+ // be send and will happily send the file descriptor on every write ...
+ String cmd = String.format("needok '%s' %s\n", needed, "ok");
+ managmentCommand(cmd);
+
+ // Set the FileDescriptor to null to stop this mad behavior
+ mSocket.setFileDescriptorsForSend(null);
+
+ pfd.close();
+
+ return true;
+ } catch (NoSuchMethodException e) {
+ exp =e;
+ } catch (IllegalArgumentException e) {
+ exp =e;
+ } catch (IllegalAccessException e) {
+ exp =e;
+ } catch (InvocationTargetException e) {
+ exp =e;
+ } catch (IOException e) {
+ exp =e;
+ }
+ if(exp!=null) {
+ OpenVPN.logMessage(0,"", "Could not send fd over socket:" + exp.getLocalizedMessage());
+ exp.printStackTrace();
+ }
+ return false;
+ }
+
+ private void processPWCommand(String argument) {
+ //argument has the form Need 'Private Key' password
+ // or ">PASSWORD:Verification Failed: '%s' ['%s']"
+ String needed;
+
+
+
+ try{
+
+ int p1 = argument.indexOf('\'');
+ int p2 = argument.indexOf('\'',p1+1);
+ needed = argument.substring(p1+1, p2);
+ if (argument.startsWith("Verification Failed")) {
+ proccessPWFailed(needed, argument.substring(p2+1));
+ return;
+ }
+ } catch (StringIndexOutOfBoundsException sioob) {
+ OpenVPN.logMessage(0, "", "Could not parse management Password command: " + argument);
+ return;
+ }
+
+ String pw=null;
+
+ if(needed.equals("Private Key")) {
+ pw = mProfile.getPasswordPrivateKey();
+ } else if (needed.equals("Auth")) {
+ String usercmd = String.format("username '%s' %s\n",
+ needed, VpnProfile.openVpnEscape(mProfile.mUsername));
+ managmentCommand(usercmd);
+ pw = mProfile.getPasswordAuth();
+ }
+ if(pw!=null) {
+ String cmd = String.format("password '%s' %s\n", needed, VpnProfile.openVpnEscape(pw));
+ managmentCommand(cmd);
+ } else {
+ OpenVPN.logMessage(0, OpenVPN.MANAGMENT_PREFIX, String.format("Openvpn requires Authentication type '%s' but no password/key information available", needed));
+ }
+
+ }
+
+
+
+
+ private void proccessPWFailed(String needed, String args) {
+ OpenVPN.updateStateString("AUTH_FAILED", needed + args,R.string.state_auth_failed,ConnectionStatus.LEVEL_AUTH_FAILED);
+ }
+ private void logStatusMessage(String command) {
+ OpenVPN.logMessage(0,"MGMT:", command);
+ }
+
+
+ private static boolean stopOpenVPN() {
+ boolean sendCMD=false;
+ for (OpenVpnManagementThread mt: active){
+ mt.managmentCommand("signal SIGINT\n");
+ sendCMD=true;
+ try {
+ if(mt.mSocket !=null)
+ mt.mSocket.close();
+ } catch (IOException e) {
+ // Ignore close error on already closed socket
+ }
+ }
+ return sendCMD;
+ }
+
+ public void signalusr1() {
+ mReleaseHold=false;
+ if(!mWaitingForRelease)
+ managmentCommand("signal SIGUSR1\n");
+ }
+
+ public void reconnect() {
+ signalusr1();
+ releaseHold();
+ }
+
+ private void processSignCommand(String b64data) {
+
+ String signed_string = mProfile.getSignedData(b64data);
+ managmentCommand("rsa-sig\n");
+ managmentCommand(signed_string);
+ managmentCommand("\nEND\n");
+ }
+
+ @Override
+ public void pause() {
+ signalusr1();
+ }
+
+ @Override
+ public void resume() {
+ releaseHold();
+ }
+
+ @Override
+ public boolean stopVPN() {
+ return stopOpenVPN();
+ }
+}
diff --git a/src/de/blinkt/openvpn/core/OpenVpnService.java b/src/de/blinkt/openvpn/core/OpenVpnService.java
new file mode 100644
index 00000000..a6ec1a1d
--- /dev/null
+++ b/src/de/blinkt/openvpn/core/OpenVpnService.java
@@ -0,0 +1,625 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package de.blinkt.openvpn.core;import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Locale;
+import java.util.Vector;
+
+import android.Manifest.permission;
+import android.annotation.TargetApi;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.net.ConnectivityManager;
+import android.net.LocalServerSocket;
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.net.VpnService;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Handler.Callback;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.preference.PreferenceManager;
+import de.blinkt.openvpn.LogWindow;
+import de.blinkt.openvpn.R;
+import de.blinkt.openvpn.VpnProfile;
+import de.blinkt.openvpn.core.OpenVPN.ByteCountListener;
+import de.blinkt.openvpn.core.OpenVPN.ConnectionStatus;
+import de.blinkt.openvpn.core.OpenVPN.StateListener;
+
+public class OpenVpnService extends VpnService implements StateListener, Callback, ByteCountListener {
+ public static final String START_SERVICE = "de.blinkt.openvpn.START_SERVICE";
+ public static final String START_SERVICE_STICKY = "de.blinkt.openvpn.START_SERVICE_STICKY";
+ public static final String ALWAYS_SHOW_NOTIFICATION = "de.blinkt.openvpn.NOTIFICATION_ALWAYS_VISIBLE";
+
+
+ private Thread mProcessThread=null;
+
+ private Vector<String> mDnslist=new Vector<String>();
+
+ private VpnProfile mProfile;
+
+ private String mDomain=null;
+
+ private Vector<CIDRIP> mRoutes=new Vector<CIDRIP>();
+ private Vector<String> mRoutesv6=new Vector<String>();
+
+ private CIDRIP mLocalIP=null;
+
+ private int mMtu;
+ private String mLocalIPv6=null;
+ private NetworkSateReceiver mNetworkStateReceiver;
+
+ private boolean mDisplayBytecount=false;
+
+ private boolean mStarting=false;
+
+ private long mConnecttime;
+
+
+ private static final int OPENVPN_STATUS = 1;
+
+ public static final int PROTECT_FD = 0;
+
+ private static boolean mNotificationalwaysVisible=false;
+
+ private final IBinder mBinder = new LocalBinder();
+ private boolean mOvpn3;
+ private Thread mSocketManagerThread;
+ private OpenVPNMangement mManagement;
+
+ public class LocalBinder extends Binder {
+ public OpenVpnService getService() {
+ // Return this instance of LocalService so clients can call public methods
+ return OpenVpnService.this;
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ String action = intent.getAction();
+ if( action !=null && action.equals(START_SERVICE))
+ return mBinder;
+ else
+ return super.onBind(intent);
+ }
+
+ @Override
+ public void onRevoke() {
+ mManagement.stopVPN();
+ endVpnService();
+ }
+
+ // Similar to revoke but do not try to stop process
+ public void processDied() {
+ endVpnService();
+ }
+
+ private void endVpnService() {
+ mProcessThread=null;
+ OpenVPN.logBuilderConfig(null);
+ OpenVPN.removeByteCountListener(this);
+ unregisterNetworkStateReceiver();
+ ProfileManager.setConntectedVpnProfileDisconnected(this);
+ if(!mStarting) {
+ stopForeground(!mNotificationalwaysVisible);
+
+ if( !mNotificationalwaysVisible) {
+ stopSelf();
+ OpenVPN.removeStateListener(this);
+ }
+ }
+ }
+
+ private void showNotification(String msg, String tickerText, boolean lowpriority, long when, ConnectionStatus level) {
+ String ns = Context.NOTIFICATION_SERVICE;
+ NotificationManager mNotificationManager = (NotificationManager) getSystemService(ns);
+
+
+ int icon = R.drawable.ic_notification_icon;
+ android.app.Notification.Builder nbuilder = new Notification.Builder(this);
+
+ if(mProfile!=null)
+ nbuilder.setContentTitle(getString(R.string.notifcation_title,mProfile.mName));
+ else
+ nbuilder.setContentTitle(getString(R.string.notifcation_title_notconnect));
+
+ nbuilder.setContentText(msg);
+ nbuilder.setOnlyAlertOnce(true);
+ nbuilder.setOngoing(true);
+ nbuilder.setContentIntent(getLogPendingIntent());
+ nbuilder.setSmallIcon(icon,level.level);
+ if(when !=0)
+ nbuilder.setWhen(when);
+
+
+ // Try to set the priority available since API 16 (Jellybean)
+ jbNotificationExtras(lowpriority, nbuilder);
+ if(tickerText!=null && !tickerText.equals(""))
+ nbuilder.setTicker(tickerText);
+
+ @SuppressWarnings("deprecation")
+ Notification notification = nbuilder.getNotification();
+
+
+ mNotificationManager.notify(OPENVPN_STATUS, notification);
+ startForeground(OPENVPN_STATUS, notification);
+ }
+
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ private void jbNotificationExtras(boolean lowpriority,
+ android.app.Notification.Builder nbuilder) {
+ try {
+ if(lowpriority) {
+ Method setpriority = nbuilder.getClass().getMethod("setPriority", int.class);
+ // PRIORITY_MIN == -2
+ setpriority.invoke(nbuilder, -2 );
+
+ Method setUsesChronometer = nbuilder.getClass().getMethod("setUsesChronometer", boolean.class);
+ setUsesChronometer.invoke(nbuilder,true);
+
+ /* PendingIntent cancelconnet=null;
+
+ nbuilder.addAction(android.R.drawable.ic_menu_close_clear_cancel,
+ getString(R.string.cancel_connection),cancelconnet); */
+ }
+
+ //ignore exception
+ } catch (NoSuchMethodException nsm) {
+ } catch (IllegalArgumentException e) {
+ } catch (IllegalAccessException e) {
+ } catch (InvocationTargetException e) {
+ }
+
+ }
+
+ PendingIntent getLogPendingIntent() {
+ // Let the configure Button show the Log
+ Intent intent = new Intent(getBaseContext(),LogWindow.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+ PendingIntent startLW = PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
+ intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+ return startLW;
+
+ }
+
+
+ private LocalServerSocket openManagmentInterface(int tries) {
+ // Could take a while to open connection
+ String socketname = (getCacheDir().getAbsolutePath() + "/" + "mgmtsocket");
+ // The sock is transfered to the LocalServerSocket, ignore warning
+ @SuppressWarnings("resource")
+ LocalSocket sock = new LocalSocket();
+
+ while(tries > 0 && !sock.isConnected()) {
+ try {
+ sock.bind(new LocalSocketAddress(socketname,
+ LocalSocketAddress.Namespace.FILESYSTEM));
+ } catch (IOException e) {
+ // wait 300 ms before retrying
+ try { Thread.sleep(300);
+ } catch (InterruptedException e1) {}
+
+ }
+ tries--;
+ }
+
+ try {
+ LocalServerSocket lss = new LocalServerSocket(sock.getFileDescriptor());
+ return lss;
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+
+
+ }
+
+ synchronized void registerNetworkStateReceiver(OpenVPNMangement magnagement) {
+ // Registers BroadcastReceiver to track network connection changes.
+ IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
+ mNetworkStateReceiver = new NetworkSateReceiver(magnagement);
+ registerReceiver(mNetworkStateReceiver, filter);
+ }
+
+ synchronized void unregisterNetworkStateReceiver() {
+ if(mNetworkStateReceiver!=null)
+ this.unregisterReceiver(mNetworkStateReceiver);
+ mNetworkStateReceiver=null;
+ }
+
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+
+ if(intent != null && intent.getBooleanExtra(ALWAYS_SHOW_NOTIFICATION, false))
+ mNotificationalwaysVisible=true;
+
+ OpenVPN.addStateListener(this);
+ OpenVPN.addByteCountListener(this);
+
+ if(intent != null && intent.getAction() !=null &&intent.getAction().equals(START_SERVICE))
+ return START_NOT_STICKY;
+ if(intent != null && intent.getAction() !=null &&intent.getAction().equals(START_SERVICE_STICKY)) {
+ return START_REDELIVER_INTENT;
+ }
+
+
+ // Extract information from the intent.
+ String prefix = getPackageName();
+ String[] argv = intent.getStringArrayExtra(prefix + ".ARGV");
+ String nativelibdir = intent.getStringExtra(prefix + ".nativelib");
+ String profileUUID = intent.getStringExtra(prefix + ".profileUUID");
+
+ mProfile = ProfileManager.get(profileUUID);
+
+ showNotification("Starting VPN " + mProfile.mName,"Starting VPN " + mProfile.mName,
+ false,0,ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET);
+
+ // Set a flag that we are starting a new VPN
+ mStarting=true;
+ // Stop the previous session by interrupting the thread.
+ if(mManagement!=null && mManagement.stopVPN())
+ // an old was asked to exit, wait 1s
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ }
+
+
+ if (mProcessThread!=null) {
+ mProcessThread.interrupt();
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ }
+ }
+ // An old running VPN should now be exited
+ mStarting=false;
+
+
+ // Open the Management Interface
+ if(!mOvpn3) {
+ LocalServerSocket mgmtsocket = openManagmentInterface(8);
+
+ if(mgmtsocket!=null) {
+ // start a Thread that handles incoming messages of the managment socket
+ OpenVpnManagementThread ovpnmgmthread = new OpenVpnManagementThread(mProfile,mgmtsocket,this);
+ mSocketManagerThread = new Thread(ovpnmgmthread,"OpenVPNMgmtThread");
+ mSocketManagerThread.start();
+ mManagement= ovpnmgmthread;
+ OpenVPN.logInfo("started Socket Thread");
+ }
+ }
+
+ // Start a new session by creating a new thread.
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+
+ mOvpn3 = prefs.getBoolean("ovpn3", false);
+ mOvpn3 = false;
+
+ Runnable processThread;
+ if(mOvpn3) {
+
+ OpenVPNMangement mOpenVPN3 = instantiateOpenVPN3Core();
+ processThread = (Runnable) mOpenVPN3;
+ mManagement = mOpenVPN3;
+
+
+ } else {
+ processThread = new OpenVPNThread(this, argv,nativelibdir);
+ }
+
+ mProcessThread = new Thread(processThread, "OpenVPNProcessThread");
+ mProcessThread.start();
+
+ registerNetworkStateReceiver(mManagement);
+
+
+ ProfileManager.setConnectedVpnProfile(this, mProfile);
+
+ return START_NOT_STICKY;
+ }
+
+ private OpenVPNMangement instantiateOpenVPN3Core() {
+ return null;
+ }
+
+ @Override
+ public void onDestroy() {
+ if (mProcessThread != null) {
+ mManagement.stopVPN();
+
+ mProcessThread.interrupt();
+ }
+ if (mNetworkStateReceiver!= null) {
+ this.unregisterReceiver(mNetworkStateReceiver);
+ }
+ // Just in case unregister for state
+ OpenVPN.removeStateListener(this);
+
+ }
+
+
+
+ public ParcelFileDescriptor openTun() {
+ Builder builder = new Builder();
+
+ if(mLocalIP==null && mLocalIPv6==null) {
+ OpenVPN.logMessage(0, "", getString(R.string.opentun_no_ipaddr));
+ return null;
+ }
+
+ if(mLocalIP!=null) {
+ builder.addAddress(mLocalIP.mIp, mLocalIP.len);
+ }
+
+ if(mLocalIPv6!=null) {
+ String[] ipv6parts = mLocalIPv6.split("/");
+ builder.addAddress(ipv6parts[0],Integer.parseInt(ipv6parts[1]));
+ }
+
+
+ for (String dns : mDnslist ) {
+ try {
+ builder.addDnsServer(dns);
+ } catch (IllegalArgumentException iae) {
+ OpenVPN.logError(R.string.dns_add_error, dns,iae.getLocalizedMessage());
+ }
+ }
+
+
+ builder.setMtu(mMtu);
+
+
+ for (CIDRIP route:mRoutes) {
+ try {
+ builder.addRoute(route.mIp, route.len);
+ } catch (IllegalArgumentException ia) {
+ OpenVPN.logMessage(0, "", getString(R.string.route_rejected) + route + " " + ia.getLocalizedMessage());
+ }
+ }
+
+ for(String v6route:mRoutesv6) {
+ try {
+ String[] v6parts = v6route.split("/");
+ builder.addRoute(v6parts[0],Integer.parseInt(v6parts[1]));
+ } catch (IllegalArgumentException ia) {
+ OpenVPN.logMessage(0, "", getString(R.string.route_rejected) + v6route + " " + ia.getLocalizedMessage());
+ }
+ }
+
+ if(mDomain!=null)
+ builder.addSearchDomain(mDomain);
+
+ String bconfig[] = new String[6];
+
+ bconfig[0]= getString(R.string.last_openvpn_tun_config);
+ bconfig[1] = getString(R.string.local_ip_info,mLocalIP.mIp,mLocalIP.len,mLocalIPv6, mMtu);
+ bconfig[2] = getString(R.string.dns_server_info, joinString(mDnslist));
+ bconfig[3] = getString(R.string.dns_domain_info, mDomain);
+ bconfig[4] = getString(R.string.routes_info, joinString(mRoutes));
+ bconfig[5] = getString(R.string.routes_info6, joinString(mRoutesv6));
+
+ String session = mProfile.mName;
+ if(mLocalIP!=null && mLocalIPv6!=null)
+ session = getString(R.string.session_ipv6string,session, mLocalIP, mLocalIPv6);
+ else if (mLocalIP !=null)
+ session= getString(R.string.session_ipv4string, session, mLocalIP);
+
+ builder.setSession(session);
+
+
+ OpenVPN.logBuilderConfig(bconfig);
+
+ // No DNS Server, log a warning
+ if(mDnslist.size()==0)
+ OpenVPN.logInfo(R.string.warn_no_dns);
+
+ // Reset information
+ mDnslist.clear();
+ mRoutes.clear();
+ mRoutesv6.clear();
+ mLocalIP=null;
+ mLocalIPv6=null;
+ mDomain=null;
+
+ builder.setConfigureIntent(getLogPendingIntent());
+
+ try {
+ ParcelFileDescriptor pfd = builder.establish();
+ return pfd;
+ } catch (Exception e) {
+ OpenVPN.logMessage(0, "", getString(R.string.tun_open_error));
+ OpenVPN.logMessage(0, "", getString(R.string.error) + e.getLocalizedMessage());
+ OpenVPN.logMessage(0, "", getString(R.string.tun_error_helpful));
+ return null;
+ }
+
+ }
+
+
+ // Ugly, but java has no such method
+ private <T> String joinString(Vector<T> vec) {
+ String ret = "";
+ if(vec.size() > 0){
+ ret = vec.get(0).toString();
+ for(int i=1;i < vec.size();i++) {
+ ret = ret + ", " + vec.get(i).toString();
+ }
+ }
+ return ret;
+ }
+
+
+
+
+
+
+ public void addDNS(String dns) {
+ mDnslist.add(dns);
+ }
+
+
+ public void setDomain(String domain) {
+ if(mDomain==null) {
+ mDomain=domain;
+ }
+ }
+
+
+ public void addRoute(CIDRIP route)
+ {
+ mRoutes.add(route );
+ }
+ public void addRoute(String dest, String mask) {
+ CIDRIP route = new CIDRIP(dest, mask);
+ if(route.len == 32 && !mask.equals("255.255.255.255")) {
+ OpenVPN.logMessage(0, "", getString(R.string.route_not_cidr,dest,mask));
+ }
+
+ if(route.normalise())
+ OpenVPN.logMessage(0, "", getString(R.string.route_not_netip,dest,route.len,route.mIp));
+
+ mRoutes.add(route);
+ }
+
+ public void addRoutev6(String extra) {
+ mRoutesv6.add(extra);
+ }
+
+ public void setMtu(int mtu) {
+ mMtu=mtu;
+ }
+
+ public void setLocalIP(CIDRIP cdrip)
+ {
+ mLocalIP=cdrip;
+ }
+
+
+ public void setLocalIP(String local, String netmask,int mtu, String mode) {
+ mLocalIP = new CIDRIP(local, netmask);
+ mMtu = mtu;
+
+ if(mLocalIP.len == 32 && !netmask.equals("255.255.255.255")) {
+ // get the netmask as IP
+ long netint = CIDRIP.getInt(netmask);
+ if(Math.abs(netint - mLocalIP.getInt()) ==1) {
+ if("net30".equals(mode))
+ mLocalIP.len=30;
+ else
+ mLocalIP.len=31;
+ } else {
+ OpenVPN.logMessage(0, "", getString(R.string.ip_not_cidr, local,netmask,mode));
+ }
+ }
+ }
+
+ public void setLocalIPv6(String ipv6addr) {
+ mLocalIPv6 = ipv6addr;
+ }
+
+ @Override
+ public void updateState(String state,String logmessage, int resid, ConnectionStatus level) {
+ // If the process is not running, ignore any state,
+ // Notification should be invisible in this state
+ doSendBroadcast(state, level);
+ if(mProcessThread==null && !mNotificationalwaysVisible)
+ return;
+
+ // Display byte count only after being connected
+
+ {
+ if(level == ConnectionStatus.LEVEL_CONNECTED) {
+ mDisplayBytecount = true;
+ mConnecttime = System.currentTimeMillis();
+ } else {
+ mDisplayBytecount = false;
+ }
+
+ // Other notifications are shown,
+ // This also mean we are no longer connected, ignore bytecount messages until next
+ // CONNECTED
+ String ticker = getString(resid);
+ showNotification(getString(resid) +" " + logmessage,ticker,false,0, level);
+
+ }
+ }
+
+ private void doSendBroadcast(String state, ConnectionStatus level) {
+ Intent vpnstatus = new Intent();
+ vpnstatus.setAction("de.blinkt.openvpn.VPN_STATUS");
+ vpnstatus.putExtra("status", level.toString());
+ vpnstatus.putExtra("detailstatus", state);
+ sendBroadcast(vpnstatus, permission.ACCESS_NETWORK_STATE);
+ }
+
+ @Override
+ public void updateByteCount(long in, long out, long diffin, long diffout) {
+ if(mDisplayBytecount) {
+ String netstat = String.format(getString(R.string.statusline_bytecount),
+ humanReadableByteCount(in, false),
+ humanReadableByteCount(diffin/OpenVPNMangement.mBytecountinterval, true),
+ humanReadableByteCount(out, false),
+ humanReadableByteCount(diffout/OpenVPNMangement.mBytecountinterval, true));
+
+ boolean lowpriority = !mNotificationalwaysVisible;
+ showNotification(netstat,null,lowpriority,mConnecttime, ConnectionStatus.LEVEL_CONNECTED);
+ }
+
+ }
+
+ // From: http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java
+ public static String humanReadableByteCount(long bytes, boolean mbit) {
+ if(mbit)
+ bytes = bytes *8;
+ int unit = mbit ? 1000 : 1024;
+ if (bytes < unit)
+ return bytes + (mbit ? " bit" : " B");
+
+ int exp = (int) (Math.log(bytes) / Math.log(unit));
+ String pre = (mbit ? "kMGTPE" : "KMGTPE").charAt(exp-1) + (mbit ? "" : "");
+ if(mbit)
+ return String.format(Locale.getDefault(),"%.1f %sbit", bytes / Math.pow(unit, exp), pre);
+ else
+ return String.format(Locale.getDefault(),"%.1f %sB", bytes / Math.pow(unit, exp), pre);
+ }
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ Runnable r = msg.getCallback();
+ if(r!=null){
+ r.run();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public OpenVPNMangement getManagement() {
+ return mManagement;
+ }
+}
diff --git a/src/de/blinkt/openvpn/core/ProfileManager.java b/src/de/blinkt/openvpn/core/ProfileManager.java
new file mode 100644
index 00000000..d1c4afc1
--- /dev/null
+++ b/src/de/blinkt/openvpn/core/ProfileManager.java
@@ -0,0 +1,223 @@
+package de.blinkt.openvpn.core;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.StreamCorruptedException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+
+import de.blinkt.openvpn.VpnProfile;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.preference.PreferenceManager;
+
+public class ProfileManager {
+ private static final String PREFS_NAME = "VPNList";
+
+
+
+ private static final String ONBOOTPROFILE = "onBootProfile";
+
+
+
+ private static ProfileManager instance;
+
+
+
+ private static VpnProfile mLastConnectedVpn=null;
+ private HashMap<String,VpnProfile> profiles=new HashMap<String, VpnProfile>();
+ private static VpnProfile tmpprofile=null;
+
+
+ public static VpnProfile get(String key) {
+ if (tmpprofile!=null && tmpprofile.getUUIDString().equals(key))
+ return tmpprofile;
+
+ if(instance==null)
+ return null;
+ return instance.profiles.get(key);
+
+ }
+
+
+
+ private ProfileManager() { }
+
+ private static void checkInstance(Context context) {
+ if(instance == null) {
+ instance = new ProfileManager();
+ instance.loadVPNList(context);
+ }
+ }
+
+ synchronized public static ProfileManager getInstance(Context context) {
+ checkInstance(context);
+ return instance;
+ }
+
+ public static void setConntectedVpnProfileDisconnected(Context c) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c);
+ Editor prefsedit = prefs.edit();
+ prefsedit.putString(ONBOOTPROFILE, null);
+ prefsedit.apply();
+
+ }
+
+ public static void setConnectedVpnProfile(Context c, VpnProfile connectedrofile) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c);
+ Editor prefsedit = prefs.edit();
+
+ prefsedit.putString(ONBOOTPROFILE, connectedrofile.getUUIDString());
+ prefsedit.apply();
+ mLastConnectedVpn=connectedrofile;
+
+ }
+
+ public static VpnProfile getOnBootProfile(Context c) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c);
+
+ boolean useStartOnBoot = prefs.getBoolean("restartvpnonboot", false);
+
+
+ String mBootProfileUUID = prefs.getString(ONBOOTPROFILE,null);
+ if(useStartOnBoot && mBootProfileUUID!=null)
+ return get(c, mBootProfileUUID);
+ else
+ return null;
+ }
+
+
+
+
+ public Collection<VpnProfile> getProfiles() {
+ return profiles.values();
+ }
+
+ public VpnProfile getProfileByName(String name) {
+ for (VpnProfile vpnp : profiles.values()) {
+ if(vpnp.getName().equals(name)) {
+ return vpnp;
+ }
+ }
+ return null;
+ }
+
+ public void saveProfileList(Context context) {
+ SharedPreferences sharedprefs = context.getSharedPreferences(PREFS_NAME,Activity.MODE_PRIVATE);
+ Editor editor = sharedprefs.edit();
+ editor.putStringSet("vpnlist", profiles.keySet());
+
+ // For reasing I do not understand at all
+ // Android saves my prefs file only one time
+ // if I remove the debug code below :(
+ int counter = sharedprefs.getInt("counter", 0);
+ editor.putInt("counter", counter+1);
+ editor.apply();
+
+ }
+
+ public void addProfile(VpnProfile profile) {
+ profiles.put(profile.getUUID().toString(),profile);
+
+ }
+
+ public static void setTemporaryProfile(VpnProfile tmp) {
+ ProfileManager.tmpprofile = tmp;
+ }
+
+
+ public void saveProfile(Context context,VpnProfile profile) {
+ // First let basic settings save its state
+
+ ObjectOutputStream vpnfile;
+ try {
+ vpnfile = new ObjectOutputStream(context.openFileOutput((profile.getUUID().toString() + ".vp"),Activity.MODE_PRIVATE));
+
+ vpnfile.writeObject(profile);
+ vpnfile.flush();
+ vpnfile.close();
+ } catch (FileNotFoundException e) {
+
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+
+ e.printStackTrace();
+ throw new RuntimeException(e);
+
+ }
+ }
+
+
+ private void loadVPNList(Context context) {
+ profiles = new HashMap<String, VpnProfile>();
+ SharedPreferences listpref = context.getSharedPreferences(PREFS_NAME,Activity.MODE_PRIVATE);
+ Set<String> vlist = listpref.getStringSet("vpnlist", null);
+ Exception exp =null;
+ if(vlist==null){
+ vlist = new HashSet<String>();
+ }
+
+ for (String vpnentry : vlist) {
+ try {
+ ObjectInputStream vpnfile = new ObjectInputStream(context.openFileInput(vpnentry + ".vp"));
+ VpnProfile vp = ((VpnProfile) vpnfile.readObject());
+
+ // Sanity check
+ if(vp==null || vp.mName==null || vp.getUUID()==null)
+ continue;
+
+ profiles.put(vp.getUUID().toString(), vp);
+
+ } catch (StreamCorruptedException e) {
+ exp=e;
+ } catch (FileNotFoundException e) {
+ exp=e;
+ } catch (IOException e) {
+ exp=e;
+ } catch (ClassNotFoundException e) {
+ exp=e;
+ }
+ if(exp!=null) {
+ exp.printStackTrace();
+ }
+ }
+ }
+
+ public int getNumberOfProfiles() {
+ return profiles.size();
+ }
+
+
+
+ public void removeProfile(Context context,VpnProfile profile) {
+ String vpnentry = profile.getUUID().toString();
+ profiles.remove(vpnentry);
+ saveProfileList(context);
+ context.deleteFile(vpnentry + ".vp");
+ if(mLastConnectedVpn==profile)
+ mLastConnectedVpn=null;
+
+ }
+
+
+
+ public static VpnProfile get(Context context, String profileUUID) {
+ checkInstance(context);
+ return get(profileUUID);
+ }
+
+
+
+ public static VpnProfile getLastConnectedVpn() {
+ return mLastConnectedVpn;
+ }
+
+}
diff --git a/src/de/blinkt/openvpn/core/ProxyDetection.java b/src/de/blinkt/openvpn/core/ProxyDetection.java
new file mode 100644
index 00000000..bc8bf293
--- /dev/null
+++ b/src/de/blinkt/openvpn/core/ProxyDetection.java
@@ -0,0 +1,55 @@
+package de.blinkt.openvpn.core;
+
+import java.net.InetSocketAddress;
+import java.net.MalformedURLException;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.SocketAddress;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.List;
+
+import de.blinkt.openvpn.R;
+import de.blinkt.openvpn.VpnProfile;
+
+public class ProxyDetection {
+ static SocketAddress detectProxy(VpnProfile vp) {
+ // Construct a new url with https as protocol
+ try {
+ URL url = new URL(String.format("https://%s:%s",vp.mServerName,vp.mServerPort));
+ Proxy proxy = getFirstProxy(url);
+
+ if(proxy==null)
+ return null;
+ SocketAddress addr = proxy.address();
+ if (addr instanceof InetSocketAddress) {
+ return addr;
+ }
+
+ } catch (MalformedURLException e) {
+ OpenVPN.logError(R.string.getproxy_error,e.getLocalizedMessage());
+ } catch (URISyntaxException e) {
+ OpenVPN.logError(R.string.getproxy_error,e.getLocalizedMessage());
+ }
+ return null;
+ }
+
+ static Proxy getFirstProxy(URL url) throws URISyntaxException {
+ System.setProperty("java.net.useSystemProxies", "true");
+
+ List<Proxy> proxylist = ProxySelector.getDefault().select(url.toURI());
+
+
+ if (proxylist != null) {
+ for (Proxy proxy: proxylist) {
+ SocketAddress addr = proxy.address();
+
+ if (addr != null) {
+ return proxy;
+ }
+ }
+
+ }
+ return null;
+ }
+} \ No newline at end of file
diff --git a/src/de/blinkt/openvpn/core/VPNLaunchHelper.java b/src/de/blinkt/openvpn/core/VPNLaunchHelper.java
new file mode 100644
index 00000000..7d14ee6b
--- /dev/null
+++ b/src/de/blinkt/openvpn/core/VPNLaunchHelper.java
@@ -0,0 +1,76 @@
+package de.blinkt.openvpn.core;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import de.blinkt.openvpn.R;
+import de.blinkt.openvpn.VpnProfile;
+
+public class VPNLaunchHelper {
+ static private boolean writeMiniVPN(Context context) {
+ File mvpnout = new File(context.getCacheDir(),VpnProfile.MINIVPN);
+ if (mvpnout.exists() && mvpnout.canExecute())
+ return true;
+
+ IOException e2 = null;
+
+ try {
+ InputStream mvpn;
+
+ try {
+ mvpn = context.getAssets().open("minivpn." + Build.CPU_ABI);
+ }
+ catch (IOException errabi) {
+ OpenVPN.logInfo("Failed getting assets for archicture " + Build.CPU_ABI);
+ e2=errabi;
+ mvpn = context.getAssets().open("minivpn." + Build.CPU_ABI2);
+
+ }
+
+
+ FileOutputStream fout = new FileOutputStream(mvpnout);
+
+ byte buf[]= new byte[4096];
+
+ int lenread = mvpn.read(buf);
+ while(lenread> 0) {
+ fout.write(buf, 0, lenread);
+ lenread = mvpn.read(buf);
+ }
+ fout.close();
+
+ if(!mvpnout.setExecutable(true)) {
+ OpenVPN.logMessage(0, "","Failed to set minivpn executable");
+ return false;
+ }
+
+
+ return true;
+ } catch (IOException e) {
+ if(e2!=null)
+ OpenVPN.logMessage(0, "",e2.getLocalizedMessage());
+ OpenVPN.logMessage(0, "",e.getLocalizedMessage());
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+
+ public static void startOpenVpn(VpnProfile startprofile, Context context) {
+ if(!writeMiniVPN(context)) {
+ OpenVPN.logMessage(0, "", "Error writing minivpn binary");
+ return;
+ }
+ OpenVPN.logMessage(0, "", context.getString(R.string.building_configration));
+
+ Intent startVPN = startprofile.prepareIntent(context);
+ if(startVPN!=null)
+ context.startService(startVPN);
+
+ }
+}