summaryrefslogtreecommitdiff
path: root/src/de/blinkt/openvpn/core
diff options
context:
space:
mode:
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);
+
+ }
+}