public class VpnProfile implements Serializable {
    // Note that this class cannot be moved to core where it belongs since
    // the profile loading depends on it being here
    // The Serializable documentation mentions that class name change are possible
    // but the how is unclear
    //
    transient public static final long MAX_EMBED_FILE_SIZE = 2048*1024; // 2048kB
    // Don't change this, not all parts of the program use this constant
    public static final String EXTRA_PROFILEUUID = "de.blinkt.openvpn.profileUUID";
    public static final String INLINE_TAG = "[[INLINE]]";
    public static final String DISPLAYNAME_TAG = "[[NAME]]";
    public static final String MINIVPN = "miniopenvpn";
    private static final long serialVersionUID = 7085688938959334563L;
    private static final String OVPNCONFIGFILE = "android.conf";
    public static final int MAXLOGLEVEL = 4;
    public static final int CURRENT_PROFILE_VERSION = 2;
    public static String DEFAULT_DNS1 = "8.8.8.8";
    public static String DEFAULT_DNS2 = "8.8.4.4";

    public transient String mTransientPW = null;
    public transient String mTransientPCKS12PW = null;


    public static final int TYPE_CERTIFICATES = 0;
    public static final int TYPE_PKCS12 = 1;
    public static final int TYPE_KEYSTORE = 2;
    public static final int TYPE_USERPASS = 3;
    public static final int TYPE_STATICKEYS = 4;
    public static final int TYPE_USERPASS_CERTIFICATES = 5;
    public static final int TYPE_USERPASS_PKCS12 = 6;
    public static final int TYPE_USERPASS_KEYSTORE = 7;
    public static final int X509_VERIFY_TLSREMOTE = 0;
    public static final int X509_VERIFY_TLSREMOTE_COMPAT_NOREMAPPING = 1;
    public static final int X509_VERIFY_TLSREMOTE_DN = 2;
    public static final int X509_VERIFY_TLSREMOTE_RDN = 3;
    public static final int X509_VERIFY_TLSREMOTE_RDN_PREFIX = 4;
    // variable named wrong and should haven beeen transient
    // but needs to keep wrong name to guarante loading of old
    // profiles
    public transient boolean profileDleted = false;
    public int mAuthenticationType = TYPE_KEYSTORE;
    public String mName;
    public String mAlias;
    public String mClientCertFilename;
    public String mTLSAuthDirection = "";
    public String mTLSAuthFilename;
    public String mClientKeyFilename;
    public String mCaFilename;
    public boolean mUseLzo = true;
    public int[] mServerPorts = new int[]{1194};
    public boolean mUseUdp = true;
    public String mPKCS12Filename;
    public String mPKCS12Password;
    public boolean mUseTLSAuth = false;
    public String mServerName = "openvpn.blinkt.de";
    public String mDNS1 = DEFAULT_DNS1;
    public String mDNS2 = DEFAULT_DNS2;
    public String mIPv4Address;
    public String mIPv6Address;
    public boolean mOverrideDNS = false;
    public String mSearchDomain = "blinkt.de";
    public boolean mUseDefaultRoute = true;
    public boolean mUsePull = true;
    public String mCustomRoutes;
    public boolean mCheckRemoteCN = false;
    public boolean mExpectTLSCert = true;
    public String mRemoteCN = "";
    public String mPassword = "";
    public String mUsername = "";
    public boolean mRoutenopull = false;
    public boolean mUseRandomHostname = false;
    public boolean mUseFloat = false;
    public boolean mUseCustomConfig = false;
    public String mCustomConfigOptions = "";
    public String mVerb = "1";  //ignored
    public String mCipher = "";
    public boolean mNobind = false;
    public boolean mUseDefaultRoutev6 = true;
    public String mCustomRoutesv6 = "";
    public String mKeyPassword = "";
    public boolean mPersistTun = false;
    public String mConnectRetryMax = "5";
    public String mConnectRetry = "5";
    public boolean mUserEditable = true;
    public String mAuth = "";
    public int mX509AuthType = X509_VERIFY_TLSREMOTE_RDN;
    private transient PrivateKey mPrivateKey;
    // Public attributes, since I got mad with getter/setter
    // set members to default values
    private UUID mUuid;
    public boolean mAllowLocalLAN;
    private int mProfileVersion;
    public String mExcludedRoutes;
    public String mExcludedRoutesv6;
    public String mTLSCipher;

    public VpnProfile(String name) {
        mUuid = UUID.randomUUID();
        mName = name;
        mProfileVersion = CURRENT_PROFILE_VERSION;
    }

    public static String openVpnEscape(String unescaped) {
        if (unescaped == null)
            return null;
        String escapedString = unescaped.replace("\\", "\\\\");
        escapedString = escapedString.replace("\"", "\\\"");
        escapedString = escapedString.replace("\n", "\\n");

        if (escapedString.equals(unescaped) && !escapedString.contains(" ") &&
                !escapedString.contains("#") && !escapedString.contains(";"))
            return unescaped;
        else
            return '"' + escapedString + '"';
    }

    public void clearDefaults() {
        mServerName = "unknown";
        mUsePull = false;
        mUseLzo = false;
        mUseDefaultRoute = false;
        mUseDefaultRoutev6 = false;
        mExpectTLSCert = false;
        mPersistTun = false;
        mAllowLocalLAN = true;
    }

    public UUID getUUID() {
        return mUuid;

    }

    public String getName() {
        if (mName==null)
            return "No profile name";
        return mName;
    }

    public void upgradeProfile(){
        if(mProfileVersion< 2) {
            /* default to the behaviour the OS used */
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
                mAllowLocalLAN = true;
            else
                mAllowLocalLAN = false;
        }

        mProfileVersion= CURRENT_PROFILE_VERSION;
    }

    public String getConfigFile(Context context, boolean configForOvpn3) {

        File cacheDir = context.getCacheDir();
        String cfg = "";

        // Enable managment interface
        cfg += "# Enables connection to GUI\n";
        cfg += "management ";

        cfg += cacheDir.getAbsolutePath() + "/" + "mgmtsocket";
        cfg += " unix\n";
        cfg += "management-client\n";
        // Not needed, see updated man page in 2.3
        //cfg += "management-signal\n";
        cfg += "management-query-passwords\n";
        cfg += "management-hold\n\n";

        if (!configForOvpn3)
            cfg += String.format("setenv IV_GUI_VER %s \n", openVpnEscape(getVersionEnvString(context)));

        cfg += "machine-readable-output\n";


        boolean useTLSClient = (mAuthenticationType != TYPE_STATICKEYS);

        if (useTLSClient && mUsePull)
            cfg += "client\n";
        else if (mUsePull)
            cfg += "pull\n";
        else if (useTLSClient)
            cfg += "tls-client\n";


        //cfg += "verb " + mVerb + "\n";
        cfg += "verb " + MAXLOGLEVEL + "\n";

        if (mConnectRetryMax == null) {
            mConnectRetryMax = "5";
        }

        if (!mConnectRetryMax.equals("-1"))
            cfg += "connect-retry-max " + mConnectRetryMax + "\n";

        if (mConnectRetry == null)
            mConnectRetry = "5";


        cfg += "connect-retry " + mConnectRetry + "\n";

        cfg += "resolv-retry 60\n";


        // We cannot use anything else than tun
        cfg += "dev tun\n";

        int numPorts = mServerPorts.length;
        for ( int index = 0; index < numPorts; index++){
        	// Server Address
        	cfg += "remote ";
        	cfg += mServerName;
        	cfg += " ";
        	cfg += mServerPorts[index];
        	if (mUseUdp){
        		cfg += " udp\n";
        	} else {
        		cfg += " tcp-client\n";
        	}
        }
        
        if ( numPorts > 1 ){
        	cfg += "remote-random\n";
        }

        switch (mAuthenticationType) {
            case VpnProfile.TYPE_USERPASS_CERTIFICATES:
                cfg += "auth-user-pass\n";
            case VpnProfile.TYPE_CERTIFICATES:
                // Ca
                cfg += insertFileData("ca", mCaFilename);

                // Client Cert + Key
                cfg += insertFileData("key", mClientKeyFilename);
                cfg += insertFileData("cert", mClientCertFilename);

                break;
            case VpnProfile.TYPE_USERPASS_PKCS12:
                cfg += "auth-user-pass\n";
            case VpnProfile.TYPE_PKCS12:
                cfg += insertFileData("pkcs12", mPKCS12Filename);
                break;

            case VpnProfile.TYPE_USERPASS_KEYSTORE:
                cfg += "auth-user-pass\n";
            case VpnProfile.TYPE_KEYSTORE:
                if (!configForOvpn3) {
                    String[] ks = getKeyStoreCertificates(context);
                    cfg += "### From Keystore ####\n";
                    if (ks != null) {
                        cfg += "<ca>\n" + ks[0] + "\n</ca>\n";
                        if (ks[1] != null)
                            cfg += "<extra-certs>\n" + ks[1] + "\n</extra-certs>\n";
                        cfg += "<cert>\n" + ks[2] + "\n</cert>\n";
                        cfg += "management-external-key\n";
                    } else {
                        cfg += context.getString(R.string.keychain_access) + "\n";
                        if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN)
                            if (!mAlias.matches("^[a-zA-Z0-9]$"))
                                cfg += context.getString(R.string.jelly_keystore_alphanumeric_bug) + "\n";
                    }
                }
                break;
            case VpnProfile.TYPE_USERPASS:
                cfg += "auth-user-pass\n";
                cfg += insertFileData("ca", mCaFilename);
                
                // Client Cert + Key
                cfg += insertFileData("key", mClientKeyFilename);
                cfg += insertFileData("cert", mClientCertFilename);
        }

        if (mUseLzo) {
            cfg += "comp-lzo\n";
        }

        if (mUseTLSAuth) {
            if (mAuthenticationType == TYPE_STATICKEYS)
                cfg += insertFileData("secret", mTLSAuthFilename);
            else
                cfg += insertFileData("tls-auth", mTLSAuthFilename);

            if (nonNull(mTLSAuthDirection)) {
                cfg += "key-direction ";
                cfg += mTLSAuthDirection;
                cfg += "\n";
            }

        }

        if (!mUsePull) {
            if (nonNull(mIPv4Address))
                cfg += "ifconfig " + cidrToIPAndNetmask(mIPv4Address) + "\n";

            if (nonNull(mIPv6Address))
                cfg += "ifconfig-ipv6 " + mIPv6Address + "\n";
        }

        if (mUsePull && mRoutenopull)
            cfg += "route-nopull\n";

        String routes = "";

        if (mUseDefaultRoute)
            routes += "route 0.0.0.0 0.0.0.0 vpn_gateway\n";
        else
        {
            for (String route : getCustomRoutes(mCustomRoutes)) {
                routes += "route " + route + " vpn_gateway\n";
            }

            for (String route: getCustomRoutes(mExcludedRoutes)) {
                routes += "route " + route + " net_gateway\n";
            }
        }

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT && !mAllowLocalLAN)
            cfg+="redirect-private block-local\n";
        else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && mAllowLocalLAN)
            cfg+="redirect-private unblock-local\n";


        if (mUseDefaultRoutev6)
            cfg += "route-ipv6 ::/0\n";
        else
            for (String route : getCustomRoutesv6(mCustomRoutesv6)) {
                routes += "route-ipv6 " + route + "\n";
            }

        cfg += routes;

        if (mOverrideDNS || !mUsePull) {
            if (nonNull(mDNS1))
                cfg += "dhcp-option DNS " + mDNS1 + "\n";
            if (nonNull(mDNS2))
                cfg += "dhcp-option DNS " + mDNS2 + "\n";
            if (nonNull(mSearchDomain))
                cfg += "dhcp-option DOMAIN " + mSearchDomain + "\n";

        }

        if (mNobind)
            cfg += "nobind\n";


        // Authentication
        if (mAuthenticationType != TYPE_STATICKEYS) {
            if (mCheckRemoteCN) {
                if (mRemoteCN == null || mRemoteCN.equals(""))
                    cfg += "verify-x509-name " + mServerName + " name\n";
                else
                    switch (mX509AuthType) {

                        // 2.2 style x509 checks
                        case X509_VERIFY_TLSREMOTE_COMPAT_NOREMAPPING:
                            cfg += "compat-names no-remapping\n";
                        case X509_VERIFY_TLSREMOTE:
                            cfg += "tls-remote " + openVpnEscape(mRemoteCN) + "\n";
                            break;

                        case X509_VERIFY_TLSREMOTE_RDN:
                            cfg += "verify-x509-name " + openVpnEscape(mRemoteCN) + " name\n";
                            break;

                        case X509_VERIFY_TLSREMOTE_RDN_PREFIX:
                            cfg += "verify-x509-name " + openVpnEscape(mRemoteCN) + " name-prefix\n";
                            break;

                        case X509_VERIFY_TLSREMOTE_DN:
                            cfg += "verify-x509-name " + openVpnEscape(mRemoteCN) + "\n";
                            break;
                    }
            }
            if (mExpectTLSCert)
                cfg += "remote-cert-tls server\n";
        }

        if (nonNull(mCipher)) {
            cfg += "cipher " + mCipher + "\n";
        }
        
        if ( nonNull(mTLSCipher) ){
        	cfg += "tls-cipher " + mTLSCipher + "\n";
        }

        if (nonNull(mAuth)) {
            cfg += "auth " + mAuth + "\n";
        }

        // Obscure Settings dialog
        if (mUseRandomHostname)
            cfg += "#my favorite options :)\nremote-random-hostname\n";

        if (mUseFloat)
            cfg += "float\n";

        if (mPersistTun) {
            cfg += "persist-tun\n";
            cfg += "# persist-tun also enables pre resolving to avoid DNS resolve problem\n";
            cfg += "preresolve\n";
        }

        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
        boolean usesystemproxy = prefs.getBoolean("usesystemproxy", true);
        if (usesystemproxy) {
            cfg += "# Use system proxy setting\n";
            cfg += "management-query-proxy\n";
        }


        if (mUseCustomConfig) {
            cfg += "# Custom configuration options\n";
            cfg += "# You are on your on own here :)\n";
            cfg += mCustomConfigOptions;
            cfg += "\n";

        }


        return cfg;
    }

    public String getVersionEnvString(Context c) {
        String version = "unknown";
        try {
            PackageInfo packageinfo = c.getPackageManager().getPackageInfo(c.getPackageName(), 0);
            version = packageinfo.versionName;
        } catch (PackageManager.NameNotFoundException e) {
            VpnStatus.logException(e);
        }
        return String.format(Locale.US, "%s %s", c.getPackageName(), version);

    }

    //! Put inline data inline and other data as normal escaped filename
    private String insertFileData(String cfgentry, String filedata) {
        if (filedata == null) {
            // TODO: generate good error
            return String.format("%s %s\n", cfgentry, "missing");
        } else if (isEmbedded(filedata)) {
            String dataWithOutHeader = getEmbeddedContent(filedata);
            return String.format(Locale.ENGLISH, "<%s>\n%s\n</%s>\n", cfgentry, dataWithOutHeader, cfgentry);
        } else {
            return String.format(Locale.ENGLISH, "%s %s\n", cfgentry, openVpnEscape(filedata));
        }
    }

    private boolean nonNull(String val) {
        if (val == null || val.equals(""))
            return false;
        else
            return true;
    }

    private Collection<String> getCustomRoutes(String routes) {
        Vector<String> cidrRoutes = new Vector<String>();
        if (routes == null) {
            // No routes set, return empty vector
            return cidrRoutes;
        }
        for (String route : routes.split("[\n \t]")) {
            if (!route.equals("")) {
                String cidrroute = cidrToIPAndNetmask(route);
                if (cidrroute == null)
                    return null;

                cidrRoutes.add(cidrroute);
            }
        }

        return cidrRoutes;
    }

    private Collection<String> getCustomRoutesv6(String routes) {
        Vector<String> cidrRoutes = new Vector<String>();
        if (routes == null) {
            // No routes set, return empty vector
            return cidrRoutes;
        }
        for (String route : routes.split("[\n \t]")) {
            if (!route.equals("")) {
                cidrRoutes.add(route);
            }
        }

        return cidrRoutes;
    }

    private String cidrToIPAndNetmask(String route) {
        String[] parts = route.split("/");

        // No /xx, assume /32 as netmask
        if (parts.length == 1)
            parts = (route + "/32").split("/");

        if (parts.length != 2)
            return null;
        int len;
        try {
            len = Integer.parseInt(parts[1]);
        } catch (NumberFormatException ne) {
            return null;
        }
        if (len < 0 || len > 32)
            return null;


        long nm = 0xffffffffl;
        nm = (nm << (32 - len)) & 0xffffffffl;

        String netmask = String.format(Locale.ENGLISH, "%d.%d.%d.%d", (nm & 0xff000000) >> 24, (nm & 0xff0000) >> 16, (nm & 0xff00) >> 8, nm & 0xff);
        return parts[0] + "  " + netmask;
    }

    private String[] buildOpenvpnArgv(File cacheDir) {
        Vector<String> args = new Vector<String>();

        // Add fixed paramenters
        //args.add("/data/data/de.blinkt.openvpn/lib/openvpn");
        args.add(cacheDir.getAbsolutePath() + "/" + VpnProfile.MINIVPN);

        args.add("--config");
        args.add(cacheDir.getAbsolutePath() + "/" + OVPNCONFIGFILE);


        return args.toArray(new String[args.size()]);
    }

    public Intent prepareIntent(Context context) {
        String prefix = context.getPackageName();

        Intent intent = new Intent(context, OpenVpnService.class);

        if (mAuthenticationType == VpnProfile.TYPE_KEYSTORE || mAuthenticationType == VpnProfile.TYPE_USERPASS_KEYSTORE) {
            if (getKeyStoreCertificates(context) == null)
                return null;
        }

        intent.putExtra(prefix + ".ARGV", buildOpenvpnArgv(context.getCacheDir()));
        intent.putExtra(prefix + ".profileUUID", mUuid.toString());

        ApplicationInfo info = context.getApplicationInfo();
        intent.putExtra(prefix + ".nativelib", info.nativeLibraryDir);

        try {
            FileWriter cfg = new FileWriter(context.getCacheDir().getAbsolutePath() + "/" + OVPNCONFIGFILE);
            cfg.write(getConfigFile(context, false));
            cfg.flush();
            cfg.close();
        } catch (IOException e) {
            VpnStatus.logException(e);
        }

        return intent;
    }

    public String[] getKeyStoreCertificates(Context context) {
        return getKeyStoreCertificates(context, 5);
    }

    public static String getDisplayName(String embeddedFile) {
        int start = DISPLAYNAME_TAG.length();
        int end = embeddedFile.indexOf(INLINE_TAG);
        return embeddedFile.substring(start,end);
    }

    public static String getEmbeddedContent(String data)
    {
        if (!data.contains(INLINE_TAG))
            return data;

        int start = data.indexOf(INLINE_TAG) + INLINE_TAG.length();
        return data.substring(start);
    }

    public static boolean isEmbedded(String data) {
        if (data==null)
            return false;
        if(data.startsWith(INLINE_TAG) || data.startsWith(DISPLAYNAME_TAG))
            return true;
        else
            return false;
    }


    class NoCertReturnedException extends Exception {
        public NoCertReturnedException (String msg) {
            super(msg);
        }
    }

    synchronized String[] getKeyStoreCertificates(Context context,int tries) {
        PrivateKey privateKey = null;
        X509Certificate[] cachain;
        Exception exp=null;
        try {
            privateKey = KeyChain.getPrivateKey(context, mAlias);
            mPrivateKey = privateKey;

            String keystoreChain = null;


            cachain = KeyChain.getCertificateChain(context, mAlias);
            if(cachain == null)
                throw new NoCertReturnedException("No certificate returned from Keystore");

            if (cachain.length <= 1 && !nonNull(mCaFilename)) {
                VpnStatus.logMessage(VpnStatus.LogLevel.ERROR, "", context.getString(R.string.keychain_nocacert));
            } else {
                StringWriter ksStringWriter = new StringWriter();

                PemWriter pw = new PemWriter(ksStringWriter);
                for (int i = 1; i < cachain.length; i++) {
                    X509Certificate cert = cachain[i];
                    pw.writeObject(new PemObject("CERTIFICATE", cert.getEncoded()));
                }
                pw.close();
                keystoreChain = ksStringWriter.toString();
            }


            String caout = null;
            if (nonNull(mCaFilename)) {
                try {
                    Certificate cacert = X509Utils.getCertificateFromFile(mCaFilename);
                    StringWriter caoutWriter = new StringWriter();
                    PemWriter pw = new PemWriter(caoutWriter);

                    pw.writeObject(new PemObject("CERTIFICATE", cacert.getEncoded()));
                    pw.close();
                    caout= caoutWriter.toString();

                } catch (Exception e) {
                    VpnStatus.logError("Could not read CA certificate" + e.getLocalizedMessage());
                }
            }


            StringWriter certout = new StringWriter();


            if (cachain.length >= 1) {
                X509Certificate usercert = cachain[0];

                PemWriter upw = new PemWriter(certout);
                upw.writeObject(new PemObject("CERTIFICATE", usercert.getEncoded()));
                upw.close();

            }
            String user = certout.toString();


            String ca, extra;
            if(caout==null) {
                ca =keystoreChain;
                extra=null;
            } else {
                ca = caout;
                extra=keystoreChain;
            }

            return new String[]{ca, extra, user};
        } catch (InterruptedException e) {
            exp=e;
        } catch (FileNotFoundException e) {
            exp=e;
        } catch (CertificateException e) {
            exp=e;
        } catch (IOException e) {
            exp=e;
        } catch (KeyChainException e) {
            exp=e;
        } catch (NoCertReturnedException e) {
            exp =e;
        } catch (IllegalArgumentException e) {
            exp =e;
        } catch (AssertionError e) {
            if (tries ==0)
                return null;
            VpnStatus.logError(String.format("Failure getting Keystore Keys (%s), retrying",e.getLocalizedMessage()));
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e1) {
                VpnStatus.logException(e1);
            }
            return getKeyStoreCertificates(context, tries-1);
        }
        if (exp != null) {
            exp.printStackTrace();
            VpnStatus.logError(R.string.keyChainAccessError, exp.getLocalizedMessage());

            VpnStatus.logError(R.string.keychain_access);
            if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) {
                if (!mAlias.matches("^[a-zA-Z0-9]$")) {
                    VpnStatus.logError(R.string.jelly_keystore_alphanumeric_bug);
                }
            }
        }
        return null;
    }

    //! Return an error if somethign is wrong
    public int checkProfile(Context context) {
        if (mAuthenticationType == TYPE_KEYSTORE || mAuthenticationType == TYPE_USERPASS_KEYSTORE) {
            if (mAlias == null)
                return R.string.no_keystore_cert_selected;
        }

        if (!mUsePull || mAuthenticationType == TYPE_STATICKEYS) {
            if (mIPv4Address == null || cidrToIPAndNetmask(mIPv4Address) == null)
                return R.string.ipv4_format_error;
        }
        if (!mUseDefaultRoute && (getCustomRoutes(mCustomRoutes) == null || getCustomRoutes(mExcludedRoutes) ==null))
            return R.string.custom_route_format_error;

        // Everything okay
        return R.string.no_error_found;

    }

    //! Openvpn asks for a "Private Key", this should be pkcs12 key
    //
    public String getPasswordPrivateKey() {
        if (mTransientPCKS12PW != null) {
            String pwcopy = mTransientPCKS12PW;
            mTransientPCKS12PW = null;
            return pwcopy;
        }
        switch (mAuthenticationType) {
            case TYPE_PKCS12:
            case TYPE_USERPASS_PKCS12:
                return mPKCS12Password;

            case TYPE_CERTIFICATES:
            case TYPE_USERPASS_CERTIFICATES:
                return mKeyPassword;

            case TYPE_USERPASS:
            case TYPE_STATICKEYS:
            default:
                return null;
        }
    }

    public boolean isUserPWAuth() {
        switch (mAuthenticationType) {
            case TYPE_USERPASS:
            case TYPE_USERPASS_CERTIFICATES:
            case TYPE_USERPASS_KEYSTORE:
            case TYPE_USERPASS_PKCS12:
                return true;
            default:
                return false;

        }
    }

    public boolean requireTLSKeyPassword() {
        if (!nonNull(mClientKeyFilename))
            return false;

        String data = "";
        if (isEmbedded(mClientKeyFilename))
            data = mClientKeyFilename;
        else {
            char[] buf = new char[2048];
            FileReader fr;
            try {
                fr = new FileReader(mClientKeyFilename);
                int len = fr.read(buf);
                while (len > 0) {
                    data += new String(buf, 0, len);
                    len = fr.read(buf);
                }
                fr.close();
            } catch (FileNotFoundException e) {
                return false;
            } catch (IOException e) {
                return false;
            }

        }

        if (data.contains("Proc-Type: 4,ENCRYPTED"))
            return true;
        else if (data.contains("-----BEGIN ENCRYPTED PRIVATE KEY-----"))
            return true;
        else
            return false;
    }

    public int needUserPWInput() {
        if ((mAuthenticationType == TYPE_PKCS12 || mAuthenticationType == TYPE_USERPASS_PKCS12) &&
                (mPKCS12Password == null || mPKCS12Password.equals(""))) {
            if (mTransientPCKS12PW == null)
                return R.string.pkcs12_file_encryption_key;
        }

        if (mAuthenticationType == TYPE_CERTIFICATES || mAuthenticationType == TYPE_USERPASS_CERTIFICATES) {
            if (requireTLSKeyPassword() && !nonNull(mKeyPassword))
                if (mTransientPCKS12PW == null) {
                    return R.string.private_key_password;
                }
        }

        if (isUserPWAuth() && !(nonNull(mUsername) && (nonNull(mPassword) || mTransientPW != null))) {
            return R.string.password;
        }
        return 0;
    }

    public String getPasswordAuth() {
        if (mTransientPW != null) {
            String pwcopy = mTransientPW;
            mTransientPW = null;
            return pwcopy;
        } else {
            return mPassword;
        }
    }

    // Used by the Array Adapter
    @Override
    public String toString() {
        return mName;
    }

    public String getUUIDString() {
        return mUuid.toString();
    }

    public PrivateKey getKeystoreKey() {
        return mPrivateKey;
    }

    public String getSignedData(String b64data) {
        PrivateKey privkey = getKeystoreKey();
        Exception err;

        byte[] data = Base64.decode(b64data, Base64.DEFAULT);

        // The Jelly Bean *evil* Hack
        // 4.2 implements the RSA/ECB/PKCS1PADDING in the OpenSSLprovider
        if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) {
            return processSignJellyBeans(privkey, data);
        }


        try {


            Cipher rsasinger = Cipher.getInstance("RSA/ECB/PKCS1PADDING");

            rsasinger.init(Cipher.ENCRYPT_MODE, privkey);

            byte[] signed_bytes = rsasinger.doFinal(data);
            return Base64.encodeToString(signed_bytes, Base64.NO_WRAP);

        } catch (NoSuchAlgorithmException e) {
            err = e;
        } catch (InvalidKeyException e) {
            err = e;
        } catch (NoSuchPaddingException e) {
            err = e;
        } catch (IllegalBlockSizeException e) {
            err = e;
        } catch (BadPaddingException e) {
            err = e;
        }

        VpnStatus.logError(R.string.error_rsa_sign, err.getClass().toString(), err.getLocalizedMessage());

        return null;

    }

    private String processSignJellyBeans(PrivateKey privkey, byte[] data) {
        Exception err;
        try {
            Method getKey = privkey.getClass().getSuperclass().getDeclaredMethod("getOpenSSLKey");
            getKey.setAccessible(true);

            // Real object type is OpenSSLKey
            Object opensslkey = getKey.invoke(privkey);

            getKey.setAccessible(false);

            Method getPkeyContext = opensslkey.getClass().getDeclaredMethod("getPkeyContext");

            // integer pointer to EVP_pkey
            getPkeyContext.setAccessible(true);
            int pkey = (Integer) getPkeyContext.invoke(opensslkey);
            getPkeyContext.setAccessible(false);

            // 112 with TLS 1.2 (172 back with 4.3), 36 with TLS 1.0
            byte[] signed_bytes = NativeUtils.rsasign(data, pkey);
            return Base64.encodeToString(signed_bytes, Base64.NO_WRAP);

        } catch (NoSuchMethodException e) {
            err = e;
        } catch (IllegalArgumentException e) {
            err = e;
        } catch (IllegalAccessException e) {
            err = e;
        } catch (InvocationTargetException e) {
            err = e;
        } catch (InvalidKeyException e) {
            err = e;
        }
        VpnStatus.logError(R.string.error_rsa_sign, err.getClass().toString(), err.getLocalizedMessage());

        return null;

    }


} 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>();
        Collections.addAll(rval, parts);
		return rval;

	}

	private void checkinlinefile(Vector<String> args, BufferedReader br) throws IOException, ConfigParseError {
		String arg0 = args.get(0).trim();
		// 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.trim().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", 
			"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",
            "group",
            "ip-win32",
            "management-hold",
            "management",
            "management-client",
            "management-query-remote",
			"management-query-passwords",
            "management-query-proxy",
            "management-external-key",
            "management-forget-disconnect",
            "management-signal",
            "management-log-cache",
            "management-up-down",
            "management-client-user",
            "management-client-group",
			"pause-exit",
            "plugin",
            "machine-readable-output",
			"persist-key",
			"register-dns",
			"route-delay",
			"route-gateway",
			"route-metric",
			"route-method",
			"status",
			"script-security",
			"show-net-up",
			"suppress-timestamps",
			"tmp-dir",
			"tun-ipv6",
			"topology",
            "user",
            "win-sys",
            "remote-random",

    };

    final String[][] ignoreOptionsWithArg =
    {
        {"setenv", "IV_GUI_VER"},
        {"setenv", "IV_OPENVPN_GUI_VERSION"}
    };

    final String[] connectionOptions = {
            "local",
            "remote",
            "float",
            "port",
//            "connect-retry",
            "connect-timeout",
            "connect-retry-max",
            "link-mtu",
            "tun-mtu",
            "tun-mtu-extra",
            "fragment",
            "mtu-disc",
            "local-port",
            "remote-port",
            "bind",
            "nobind",
            "proto",
            "http-proxy",
            "http-proxy-retry",
            "http-proxy-timeout",
            "http-proxy-option",
            "socks-proxy",
            "socks-proxy-retry",
            "explicit-exit-notify",
            "mssfix"
    };


    // This method is far too long
	@SuppressWarnings("ConstantConditions")
    public VpnProfile convertProfile() throws ConfigParseError{
		boolean noauthtypeset=true;
		VpnProfile np = new VpnProfile(CONVERTED_PROFILE);
		// 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 = "";
            String routeExcluded = "";
            for(Vector<String> route:routes){
				String netmask = "255.255.255.255";
                String gateway = "vpn_gateway";

                if(route.size() >= 3)
					netmask = route.get(2);
                if (route.size() >= 4)
                    gateway = route.get(3);

				String net = route.get(1);	
				try {
					CIDRIP cidr = new CIDRIP(net, netmask);
                    if (gateway.equals("net_gateway"))
                        routeExcluded += cidr.toString() + " ";
                    else
					    routeopt+=cidr.toString() + " ";
				} catch (ArrayIndexOutOfBoundsException aioob) {
					throw new ConfigParseError("Could not parse netmask of route " + netmask);
				} catch (NumberFormatException ne) {




					throw new ConfigParseError("Could not parse netmask of route " + netmask);
				}

			}
			np.mCustomRoutes=routeopt;
            np.mExcludedRoutes=routeExcluded;
		}

        Vector<Vector<String>> routesV6 = getAllOption("route-ipv6", 1, 4);
        if (routesV6!=null) {
            String customIPv6Routes = "";
            for (Vector<String> route:routesV6){
                customIPv6Routes += route.get(1) + " ";
            }

            np.mCustomRoutesv6 = customIPv6Routes;
        }

        // Also recognize tls-auth [inline] direction ...
		Vector<Vector<String>> tlsauthoptions = getAllOption("tls-auth", 1, 2);
		if(tlsauthoptions!=null) {
			for(Vector<String> tlsauth:tlsauthoptions) {
				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);

        Vector<Vector<String>> defgw = getAllOption("redirect-gateway", 0, 5);
		if(defgw != null)
        {
            np.mUseDefaultRoute=true;
            checkRedirectParameters(np, defgw);
        }

        Vector<Vector<String>> redirectPrivate = getAllOption("redirect-private",0,5);
        if (redirectPrivate != null)
        {
            checkRedirectParameters(np,redirectPrivate);
        }
		Vector<String> dev =getOption("dev",1,1);
		Vector<String> devtype =getOption("dev-type",1,1);

        if ((devtype != null && devtype.get(1).equals("tun")) ||
                (dev != null && dev.get(1).startsWith("tun")) ||
                (devtype == null && dev == null)) {
            //everything okay
        } else {
            throw new ConfigParseError("Sorry. Only tun mode is supported. See the FAQ for more detail");
		}



		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");
		}

		// Port set in config file is not used.
//		Vector<String> port = getOption("port", 1,1);
//		if(port!=null){
//			np.mServerPort = port.get(1);
//		}
//
//        Vector<String> rport = getOption("rport", 1,1);
//        if(port!=null){
//            np.mServerPort = port.get(1);
//        }

        Vector<String> proto = getOption("proto", 1,1);
		if(proto!=null){
			np.mUseUdp=isUdpProto(proto.get(1));
		}

		// 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:
				// Port set in config file is not used
			//	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) {
			try	{
				CIDRIP cidr = new CIDRIP(ifconfig.get(1), ifconfig.get(2));
				np.mIPv4Address=cidr.toString();
			} catch (NumberFormatException nfe) {
				throw new ConfigParseError("Could not pase ifconfig IP address: " + nfe.getLocalizedMessage());
			}

		}

		if(getOption("remote-random-hostname", 0, 0)!=null)
			np.mUseRandomHostname=true;

		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;
	}

    private void checkRedirectParameters(VpnProfile np, Vector<Vector<String>> defgw) {
        for (Vector<String> redirect: defgw)
            for (int i=1;i<redirect.size();i++){
                if (redirect.get(i).equals("block-local"))
                    np.mAllowLocalLAN=false;
                else if (redirect.get(i).equals("unblock-local"))
                    np.mAllowLocalLAN=true;
            }
    }

    public void useExtraRemotesAsCustom(boolean b) {
		this.extraRemotesAsCustom = b;
	}

	private boolean isUdpProto(String proto) throws ConfigParseError {
		boolean isudp;
		if(proto.equals("udp") || proto.equals("udp6"))
			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 = VpnProfile.getEmbeddedContent(inlinedata);
		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;

		}
	}


    boolean ignoreThisOption(Vector<String> option) {
        for (String[] ignoreOption : ignoreOptionsWithArg) {

            if (option.size() < ignoreOption.length)
                continue;

            boolean ignore = true;
            for (int i = 0; i < ignoreOption.length; i++) {
                if (!ignoreOption[i].equals(option.get(i)))
                    ignore = false;
            }
            if (ignore)
                return true;

        }
        return false;
    }

    private String getOptionStrings(Vector<Vector<String>> option) {
        String custom = "";
        for (Vector<String> optionsline : option) {
            if (!ignoreThisOption(optionsline)) {
                for (String arg : optionsline)
                    custom += VpnProfile.openVpnEscape(arg) + " ";
                custom += "\n";
            }
        }
        return custom;
    }


    private void fixup(VpnProfile np) {
		if(np.mRemoteCN.equals(np.mServerName)) {
			np.mRemoteCN="";
		}
	}

	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;
	}

} public class ConfigParseError extends Exception {
	private static final long serialVersionUID = -60L;

	public ConfigParseError(String msg) {
		super(msg);
	}
} public class OpenVpnService extends VpnService implements StateListener, Callback {
    public static final String START_SERVICE = "de.blinkt.openvpn.START_SERVICE";
    public static final String START_SERVICE_STICKY = "de.blinkt.openvpn.START_SERVICE_STICKY";
    public static final String ALWAYS_SHOW_NOTIFICATION = "de.blinkt.openvpn.NOTIFICATION_ALWAYS_VISIBLE";
    public static final String DISCONNECT_VPN = "de.blinkt.openvpn.DISCONNECT_VPN";
    public static final String NETWORK_CHANGED = "de.blinkt.openvpn.NETWORK_CHANGED";

    private static boolean mNotificationAlwaysVisible = true;
    private final Vector<String> mDnslist = new Vector<String>();
    private final NetworkSpace mRoutes = new NetworkSpace();
    private final NetworkSpace mRoutesv6 = new NetworkSpace();
    private final IBinder mBinder = new LocalBinder();
    private Thread mProcessThread = null;
    private VpnProfile mProfile;
    private String mDomain = null;
    private CIDRIP mLocalIP = null;
    private int mMtu;
    private String mLocalIPv6 = null;
    private boolean mStarting = false;
    private boolean mOvpn3 = false;
    private OpenVPNManagement mManagement;
    private String mLastTunCfg;
    private String mRemoteGW;
    
    private NetworkStateManager mNetworkStateManager;
	private Looper mServiceLooper;
	private ServiceHandler mServiceHandler;
	private ConnectionStatus mLevel;

    @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();
        
        /* the system revoked the rights grated with the initial prepare() call.
		 * called when the user clicks disconnect in the system's VPN dialog */
        // Please forgive this miserable sinner for creating code links up the stack.
        // I'll not import Secure Wireless which would make that a compiler requirement, so I'll just
        // copy those string values here. Yet another sin.
        // The need is to know when the user had revoked VPN permission via the white "key"
        // system notification, which is why we're here. Knowing that lets us not try to
        // reconnect as soon the user disconnects.
        // TODO: Refactor this into a sin-free state via a callback or something.
        SharedPreferences.Editor editor  = getSharedPreferences(VPNImplementation.VPN_PREFS, Context.MODE_PRIVATE).edit();
        editor.putBoolean("permission revoked", true);
        editor.putBoolean("secure me", false);
        editor.commit();
    }

    // Similar to revoke but do not try to stop process
    public void processDied() {
        endVpnService();
    }

    private void endVpnService() {
        mProcessThread = null;

        ProfileManager.setConntectedVpnProfileDisconnected(this);
        if (!mStarting) {
            stopForeground(!mNotificationAlwaysVisible);

            if (!mNotificationAlwaysVisible) {
                stopSelf();
                VpnStatus.removeStateListener(this);
            }
        }
    }
    
    private void showNotification(String msg) {
        if (msg.equals("cancel")) {
            NotificationManager mNotificationManager =
                    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            mNotificationManager.cancel(VPNImplementation.NOTIFICATION_ID);
        }
        else {
            String mainActivity = "me.disconnect.mobile.MainActivity";
            
            Notification.Builder mBuilder = new Notification.Builder(this)
                .setSmallIcon(R.drawable.notification_icon)
                .setContentTitle(getString(R.string.vpn_notification_title))
                .setContentText(msg);
            
            // Create an explicit intent for our main activity.
            Intent resultIntent = new Intent();
            resultIntent.setClassName(this, mainActivity);
            
            // The stack builder object will contain an artificial back stack for the started Activity.
            // This ensures that navigating backward from the Activity leads from app to the Home screen.
            TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
            // Add the back stack for the Intent (but not the Intent itself)
            stackBuilder.addParentStack(new ComponentName(this, mainActivity));
            // Add the Intent that starts the Activity to the top of the stack
            stackBuilder.addNextIntent(resultIntent);
            mBuilder.setContentIntent(stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT));
    //        mBuilder.setLights(Color.GREEN, 500, 500);
             
            @SuppressWarnings("deprecation") // Notification.Builder build() is > API 16
            Notification notification = mBuilder.getNotification();
            NotificationManager mNotificationManager =
                    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            // mId allows you to update the notification later on.
            mNotificationManager.notify(VPNImplementation.NOTIFICATION_ID, notification);
        }
    }
    
    private String stateToString(ConnectionStatus state) {
    	// Convert connection status
    	switch ( state ){
    	case LEVEL_CONNECTED:{
            return "cancel";
    	}
    	case LEVEL_VPNPAUSED:{
    		return getString(R.string.vpn_notification_disconnected);
    	}
    	case LEVEL_CONNECTING_SERVER_REPLIED:
    	case LEVEL_CONNECTING_NO_SERVER_REPLY_YET:
    	case LEVEL_WAITING_FOR_USER_INPUT:{
    		return getString(R.string.vpn_notification_connecting);
    	}
    	case LEVEL_NONETWORK:
    	case LEVEL_NOTCONNECTED:
    	case LEVEL_AUTH_FAILED:
    	case UNKNOWN_LEVEL:
    	default:{
    		return getString(R.string.vpn_notification_disconnected);
    	}
    	}
    }

    // TODO Change to show main activity
    PendingIntent getLogPendingIntent() {
        // Let the configure Button show the Log
    	String mainActivity = "me.disconnect.mobile.MainActivity";
		
        Intent intent = new Intent();
        intent.setClassName(this, mainActivity);

        intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
        PendingIntent startLW = PendingIntent.getActivity(this, 0, intent, 0);
        intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
        return startLW;

    }

    public void userPause(boolean shouldBePaused) {
    }
    
    @Override
    public void onCreate() {
    	super.onCreate();
    	
    	// Start up the thread running the service.  Note that we create a
        // separate thread because the service normally runs in the process's
        // main thread, which we don't want to block.  We also make it
        // background priority so CPU-intensive work will not disrupt our UI.
        HandlerThread thread = new HandlerThread("ServiceStartArguments",
        	 	android.os.Process.THREAD_PRIORITY_BACKGROUND);
        thread.start();
        
        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (intent == null)
            return START_NOT_STICKY;

        if (intent.getBooleanExtra(ALWAYS_SHOW_NOTIFICATION, false))
            mNotificationAlwaysVisible = true;

        VpnStatus.addStateListener(this);
        
        if (START_SERVICE.equals(intent.getAction()))
            return START_NOT_STICKY;
        
        if (START_SERVICE_STICKY.equals(intent.getAction())) {
            return START_REDELIVER_INTENT;
        }
        
        // TODO should network change be handled differently
        // Remove any existing messages in queue
        mServiceHandler.removeCallbacksAndMessages(null);

        // Queue message
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.arg2 = flags;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
        
        return START_NOT_STICKY;
    }
        
        
        private final class ServiceHandler extends Handler {
            public ServiceHandler(Looper looper) {
                super(looper);
            }
            
            @Override
            public void handleMessage(Message msg) {
            	Intent intent = (Intent)msg.obj;



            	// Disconnect VPN
            	if (DISCONNECT_VPN.equals(intent.getAction())){
            		ProfileManager.setConntectedVpnProfileDisconnected(OpenVpnService.this);
            		
            		stopRunningVPN();
            		return;
            	}

            	if ( NETWORK_CHANGED.equals(intent.getAction())){
            		// Notification network has changed.
            		if ( mNetworkStateManager != null ){
            			mNetworkStateManager.networkStateChange(getBaseContext());
            		}

            		return;
            	}

            	// 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(OpenVpnService.this, profileUUID);

            	showNotification(stateToString(LEVEL_CONNECTING_NO_SERVER_REPLY_YET));

            	stopRunningVPN();

            	// Start a new session by creating a new thread.
            	SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(OpenVpnService.this);

            	mOvpn3 = prefs.getBoolean("ovpn3", false);
            	if (!"ovpn3".equals(EclipseBuildConfig.FLAVOR))
            		mOvpn3 = false;


            	// Open the Management Interface
            	if (!mOvpn3) {

            		// start a Thread that handles incoming messages of the managment socket
            		OpenVpnManagementThread ovpnManagementThread = new OpenVpnManagementThread(mProfile, OpenVpnService.this);
            		if (ovpnManagementThread.openManagementInterface(OpenVpnService.this)) {

            			Thread mSocketManagerThread = new Thread(ovpnManagementThread, "OpenVPNManagementThread");
            			mSocketManagerThread.start();
            			mManagement = ovpnManagementThread;
            			VpnStatus.logInfo("started Socket Thread");
            		} else {
            			return;
            		}
            	}


            	Runnable processThread;
            	if (mOvpn3) {

            		OpenVPNManagement mOpenVPN3 = instantiateOpenVPN3Core();
            		processThread = (Runnable) mOpenVPN3;
            		mManagement = mOpenVPN3;


            	} else {
            		HashMap<String, String> env = new HashMap<String, String>();
            		processThread = new OpenVPNThread(OpenVpnService.this, argv, env, nativelibdir);
            	}

            	mProcessThread = new Thread(processThread, "OpenVPNProcessThread");
            	mProcessThread.start();

            	mManagement.resume();

            	mNetworkStateManager = new NetworkStateManager(mManagement);

            	ProfileManager.setConnectedVpnProfile(OpenVpnService.this, mProfile);



            }

			private void stopRunningVPN() {
				// 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) {
            		}
            	}            	
            	
            	mStarting = false;
			}

        };

    
    

    private OpenVPNManagement instantiateOpenVPN3Core() {
        try {
            Class<?> cl = Class.forName("de.blinkt.openvpn.core.OpenVPNThreadv3");
            return (OpenVPNManagement) cl.getConstructor(OpenVpnService.class,VpnProfile.class).newInstance(this,mProfile);
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public void onDestroy() {
        if (mProcessThread != null) {
            mManagement.stopVPN();

            mProcessThread.interrupt();
        }
       
        // Just in case unregister for state
        VpnStatus.removeStateListener(this);
        
        // Quit the service looper
        mServiceLooper.quit();
    }

    private String getTunConfigString() {
        // The format of the string is not important, only that
        // two identical configurations produce the same result
        String cfg = "TUNCFG UNQIUE STRING ips:";

        if (mLocalIP != null)
            cfg += mLocalIP.toString();
        if (mLocalIPv6 != null)
            cfg += mLocalIPv6.toString();

        cfg += "routes: " + TextUtils.join("|", mRoutes.getNetworks(true)) + TextUtils.join("|", mRoutesv6.getNetworks(true));
        cfg += "excl. routes:" + TextUtils.join("|", mRoutes.getNetworks(false)) + TextUtils.join("|", mRoutesv6.getNetworks(false));
        cfg += "dns: " + TextUtils.join("|", mDnslist);
        cfg += "domain: " + mDomain;
        cfg += "mtu: " + mMtu;
        return cfg;
    }

    public ParcelFileDescriptor openTun() {

        //Debug.startMethodTracing(getExternalFilesDir(null).toString() + "/opentun.trace", 40* 1024 * 1024);

        Builder builder = new Builder();

        VpnStatus.logInfo(R.string.last_openvpn_tun_config);


        if (mLocalIP == null && mLocalIPv6 == null) {
            VpnStatus.logError(getString(R.string.opentun_no_ipaddr));
            return null;
        }

        if (mLocalIP != null) {
            try {
                builder.addAddress(mLocalIP.mIp, mLocalIP.len);
            } catch (IllegalArgumentException iae) {
                VpnStatus.logError(R.string.dns_add_error, mLocalIP, iae.getLocalizedMessage());
                return null;
            }
        }

        if (mLocalIPv6 != null) {
            String[] ipv6parts = mLocalIPv6.split("/");
            try {
                builder.addAddress(ipv6parts[0], Integer.parseInt(ipv6parts[1]));
            } catch (IllegalArgumentException iae) {
                VpnStatus.logError(R.string.ip_add_error, mLocalIPv6, iae.getLocalizedMessage());
                return null;
            }

        }


        for (String dns : mDnslist) {
            try {
                builder.addDnsServer(dns);
            } catch (IllegalArgumentException iae) {
                VpnStatus.logError(R.string.dns_add_error, dns, iae.getLocalizedMessage());
            }
        }


        builder.setMtu(mMtu);

        Collection<ipAddress> positiveIPv4Routes = mRoutes.getPositiveIPList();
        Collection<ipAddress> positiveIPv6Routes = mRoutesv6.getPositiveIPList();

        for (NetworkSpace.ipAddress route : positiveIPv4Routes) {
            try {
                builder.addRoute(route.getIPv4Address(), route.networkMask);
            } catch (IllegalArgumentException ia) {
                VpnStatus.logError(getString(R.string.route_rejected) + route + " " + ia.getLocalizedMessage());
            }
        }

        for (NetworkSpace.ipAddress route6 : positiveIPv6Routes) {
            try {
                builder.addRoute(route6.getIPv6Address(), route6.networkMask);
            } catch (IllegalArgumentException ia) {
                VpnStatus.logError(getString(R.string.route_rejected) + route6 + " " + ia.getLocalizedMessage());
            }
        }

        if (mDomain != null)
            builder.addSearchDomain(mDomain);

        VpnStatus.logInfo(R.string.local_ip_info, mLocalIP.mIp, mLocalIP.len, mLocalIPv6, mMtu);
        VpnStatus.logInfo(R.string.dns_server_info, TextUtils.join(", ", mDnslist), mDomain);
        VpnStatus.logInfo(R.string.routes_info_incl, TextUtils.join(", ", mRoutes.getNetworks(true)), TextUtils.join(", ", mRoutesv6.getNetworks(true)));
        VpnStatus.logInfo(R.string.routes_info_excl, TextUtils.join(", ", mRoutes.getNetworks(false)),TextUtils.join(", ", mRoutesv6.getNetworks(false)));
        VpnStatus.logDebug(R.string.routes_debug, TextUtils.join(", ", positiveIPv4Routes), TextUtils.join(", ", positiveIPv6Routes));

        String session;
        if ( !ConfigParser.CONVERTED_PROFILE.equals(mProfile.mName)){
        	session = mProfile.mName;
        } else {
        	session = mProfile.mServerName;
        }

        builder.setSession(session);

        // No DNS Server, log a warning
        if (mDnslist.size() == 0)
            VpnStatus.logInfo(R.string.warn_no_dns);

        mLastTunCfg = getTunConfigString();

        // Reset information
        mDnslist.clear();
        mRoutes.clear();
        mRoutesv6.clear();
        mLocalIP = null;
        mLocalIPv6 = null;
        mDomain = null;

        builder.setConfigureIntent(getLogPendingIntent());

        try {
            ParcelFileDescriptor tun = builder.establish();
            //Debug.stopMethodTracing();
            return tun;
        } catch (Exception e) {
            VpnStatus.logError(R.string.tun_open_error);
            VpnStatus.logError(getString(R.string.error) + e.getLocalizedMessage());
            if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                VpnStatus.logError(R.string.tun_error_helpful);
            }
            return null;
        }

    }

    public void addDNS(String dns) {
        mDnslist.add(dns);
    }

    public void setDomain(String domain) {
        if (mDomain == null) {
            mDomain = domain;
        }
    }

    /** Route that is always included, used by the v3 core */
    public void addRoute (CIDRIP route) {
        mRoutes.addIP(route, true);
    }

    public void addRoute (String dest, String mask, String gateway, String device) {
        CIDRIP route = new CIDRIP(dest, mask);
        boolean include = isAndroidTunDevice(device);

        NetworkSpace.ipAddress gatewayIP = new NetworkSpace.ipAddress(new CIDRIP(gateway, 32),false);

        if (mLocalIP==null) {
            VpnStatus.logError("Local IP address unset but adding route?! This is broken! Please contact author with log");
            return;
        }
        NetworkSpace.ipAddress localNet = new NetworkSpace.ipAddress(mLocalIP,true);
        if (localNet.containsNet(gatewayIP))
            include=true;

        if (gateway!= null &&
                (gateway.equals("255.255.255.255") || gateway.equals(mRemoteGW)))
            include=true;


        if (route.len == 32 && !mask.equals("255.255.255.255")) {
            VpnStatus.logWarning(R.string.route_not_cidr, dest, mask);
        }

        if (route.normalise())
            VpnStatus.logWarning(R.string.route_not_netip, dest, route.len, route.mIp);

        mRoutes.addIP(route, include);
    }

    public void addRoutev6(String network, String device) {
        String[] v6parts = network.split("/");
        boolean included = isAndroidTunDevice(device);

        // Tun is opened after ROUTE6, no device name may be present

        try {
            Inet6Address ip = (Inet6Address) InetAddress.getAllByName(v6parts[0])[0];
            int mask = Integer.parseInt(v6parts[1]);
            mRoutesv6.addIPv6(ip, mask, included);

        } catch (UnknownHostException e) {
            VpnStatus.logException(e);
        }


    }

    private boolean isAndroidTunDevice(String device) {
        return device!=null &&
                (device.startsWith("tun") || "(null)".equals(device) || "vpnservice-tun".equals(device));
    }

    public void setMtu(int mtu) {
        mMtu = mtu;
    }

    public void setLocalIP(CIDRIP cdrip) {
        mLocalIP = cdrip;
    }

    public void setLocalIP(String local, String netmask, int mtu, String mode) {
        mLocalIP = new CIDRIP(local, netmask);
        mMtu = mtu;
        mRemoteGW=null;


        if (mLocalIP.len == 32 && !netmask.equals("255.255.255.255")) {
            // get the netmask as IP
            long netMaskAsInt = CIDRIP.getInt(netmask);

            // Netmask is Ip address +/-1, assume net30/p2p with small net
            if (Math.abs(netMaskAsInt - mLocalIP.getInt()) == 1) {
                if ("net30".equals(mode))
                    mLocalIP.len = 30;
                else
                    mLocalIP.len = 31;
            } else {
                if (!"p2p".equals(mode))
                    VpnStatus.logWarning(R.string.ip_not_cidr, local, netmask, mode);
                mRemoteGW=netmask;
            }
        }
    }

    public void setLocalIPv6(String ipv6addr) {
        mLocalIPv6 = ipv6addr;
    }

    @Override
    public void updateState(String state, String logmessage, int resid, ConnectionStatus level) {
        // If the process is not running, ignore any state,
        // Notification should be invisible in this state
        doSendBroadcast(state, level);
        if (mProcessThread == null && !mNotificationAlwaysVisible)
            return;

            if (level == LEVEL_WAITING_FOR_USER_INPUT) {
                // The user is presented a dialog of some kind, no need to inform the user
                // with a notification
                return;
            } 

            // Other notifications are shown,
            // This also mean we are no longer connected, ignore bytecount messages until next
            // CONNECTED
            // Does not work :(
            showNotification(stateToString(level));
            
            if ( level == LEVEL_CONNECTED){
            	mLevel = level;
            	// Register shared preference observer to update data usage
            	getSharedPreferences(VPNImplementation.VPN_PREFS, Context.MODE_PRIVATE).registerOnSharedPreferenceChangeListener(mPrefListener);
            } else {
            	// deregister shared preference observer 
            	getSharedPreferences(VPNImplementation.VPN_PREFS, Context.MODE_PRIVATE).unregisterOnSharedPreferenceChangeListener(mPrefListener);
            }
    }
    
    private SharedPreferences.OnSharedPreferenceChangeListener mPrefListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
        @Override
        public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
            if((key.equals(VPNImplementation.KEY_BANDWIDTH_USED) || key.equals(VPNImplementation.KEY_BANDWIDTH_LIMIT))) {
                showNotification(stateToString(mLevel));
            }
        }
    };

    private void doSendBroadcast(String state, ConnectionStatus level) {
    	// TODO decide in future if the connection state needs 
    	// to be broadcast.
//        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 boolean handleMessage(Message msg) {
        Runnable r = msg.getCallback();
        if (r != null) {
            r.run();
            return true;
        } else {
            return false;
        }
    }

    public OpenVPNManagement getManagement() {
        return mManagement;
    }

    public String getTunReopenStatus() {
        String currentConfiguration = getTunConfigString();
        if (currentConfiguration.equals(mLastTunCfg))
            return "NOACTION";
        else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
            return "OPEN_AFTER_CLOSE";
        else
            return "OPEN_BEFORE_CLOSE";
    }

    public class LocalBinder extends Binder {
        public OpenVpnService getService() {
            // Return this instance of LocalService so clients can call public methods
            return OpenVpnService.this;
        }
    }
    
    
} 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;


	private 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) {

            VpnStatus.logException("saving VPN profile", e);
			throw new RuntimeException(e);
		} catch (IOException e) {
            VpnStatus.logException("saving VPN profile", e);
			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;

                vp.upgradeProfile();
				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) {
			    VpnStatus.logException("Loading VPN List",exp);
			}
		}
	}

	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;
	}

} 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) {
				VpnStatus.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)) {
				VpnStatus.logError("Failed to set minivpn executable");
				return false;
			}
				
			
			return true;
		} catch (IOException e) {
			if(e2!=null)
				VpnStatus.logException(e2);
			VpnStatus.logException(e);

			return false;
		}
	}
	

	public static void startOpenVpn(VpnProfile startprofile, Context context) {
		if(!writeMiniVPN(context)) {
			VpnStatus.logError("Error writing minivpn binary");
			return;
		}

		VpnStatus.logInfo(R.string.building_configration);

		Intent startVPN = startprofile.prepareIntent(context);
		if(startVPN!=null)
			context.startService(startVPN);

	}
} public class VpnStatus {


	public static LinkedList<LogItem> logbuffer;

	private static Vector<LogListener> logListener;
	private static Vector<StateListener> stateListener;
	private static Vector<ByteCountListener> byteCountListener;

	private static String mLaststatemsg="";

	private static String mLaststate = "NOPROCESS";

	private static int mLastStateresid=R.string.state_noprocess;

	private static long mlastByteCount[]={0,0,0,0};

    public static void logException(LogLevel ll, String context,  Exception e) {
        StringWriter sw = new StringWriter();
        e.printStackTrace(new PrintWriter(sw));
        LogItem li;
        if (context !=null) {
            li = new LogItem(ll, R.string.unhandled_exception_context, e.getMessage(), sw.toString(), context);
        } else {
            li = new LogItem(ll, R.string.unhandled_exception, e.getMessage(), sw.toString());
        }
        newLogItem(li);
    }

    public static void logException(Exception e) {
        logException(LogLevel.ERROR, null, e);
    }

    public static void logException(String context, Exception e) {
        logException(LogLevel.ERROR, context, e);
    }

    private static final int MAXLOGENTRIES = 1000;

    public static final String MANAGMENT_PREFIX = "M:";

    public enum ConnectionStatus {
        LEVEL_CONNECTED,
        LEVEL_VPNPAUSED,
        LEVEL_CONNECTING_SERVER_REPLIED,
        LEVEL_CONNECTING_NO_SERVER_REPLY_YET,
        LEVEL_NONETWORK,
		LEVEL_NOTCONNECTED,
		LEVEL_AUTH_FAILED,
		LEVEL_WAITING_FOR_USER_INPUT,
		UNKNOWN_LEVEL
    }

    public enum LogLevel {
        INFO(2),
        ERROR(-2),
        WARNING(1),
        VERBOSE(3),
        DEBUG(4);

        protected int mValue;
        LogLevel(int value) {
            mValue = value;
        }

        public int getInt() {
            return mValue;
        }

        public static LogLevel getEnumByValue(int value) {
            switch (value) {
                case 1:   return INFO;
                case 2:   return ERROR;
                case 3: return WARNING;
                case 4: return DEBUG;
                default:  return null;
            }
        }
    }

    // keytool -printcert -jarfile de.blinkt.openvpn_85.apk
	public static final byte[] officalkey = {-58, -42, -44, -106, 90, -88, -87, -88, -52, -124, 84, 117, 66, 79, -112, -111, -46, 86, -37, 109};
	public static final byte[] officaldebugkey = {-99, -69, 45, 71, 114, -116, 82, 66, -99, -122, 50, -70, -56, -111, 98, -35, -65, 105, 82, 43};
	public static final byte[] amazonkey = {-116, -115, -118, -89, -116, -112, 120, 55, 79, -8, -119, -23, 106, -114, -85, -56, -4, 105, 26, -57};
    public static final byte[] fdroidkey = {-92, 111, -42, -46, 123, -96, -60, 79, -27, -31, 49, 103, 11, -54, -68, -27, 17, 2, 121, 104};


	private static ConnectionStatus mLastLevel=ConnectionStatus.LEVEL_NOTCONNECTED;

	static {
		logbuffer  = new LinkedList<LogItem>();
		logListener = new Vector<VpnStatus.LogListener>();
		stateListener = new Vector<VpnStatus.StateListener>();
		byteCountListener = new Vector<VpnStatus.ByteCountListener>();
		logInformation();
	}


	public static class LogItem implements Parcelable {


		private Object [] mArgs = null;
		private String mMessage = null;
		private int mRessourceId;
		// Default log priority
		LogLevel mLevel = LogLevel.INFO;
		private long logtime = System.currentTimeMillis();
        private int mVerbosityLevel = -1;

		private LogItem(int ressourceId, Object[] args) {
			mRessourceId = ressourceId;
			mArgs = args;
		}

        public LogItem(LogLevel level, int verblevel, String message) {
            mMessage=message;
            mLevel = level;
            mVerbosityLevel = verblevel;
        }

        @Override
		public int describeContents() {
			return 0;
		}


        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeArray(mArgs);
            dest.writeString(mMessage);
            dest.writeInt(mRessourceId);
            dest.writeInt(mLevel.getInt());
            dest.writeInt(mVerbosityLevel);

            dest.writeLong(logtime);
        }

		public LogItem(Parcel in) {
			mArgs = in.readArray(Object.class.getClassLoader());
			mMessage = in.readString();
			mRessourceId = in.readInt();
			mLevel = LogLevel.getEnumByValue(in.readInt());
            mVerbosityLevel = in.readInt();
			logtime = in.readLong();
		}

		public static final Parcelable.Creator<LogItem> CREATOR
		= new Parcelable.Creator<LogItem>() {
			public LogItem createFromParcel(Parcel in) {
				return new LogItem(in);
			}

			public LogItem[] newArray(int size) {
				return new LogItem[size];
			}
		};

        public LogItem(LogLevel loglevel,int ressourceId, Object... args) {
            mRessourceId = ressourceId;
            mArgs =args;
            mLevel = loglevel;
        }


		public LogItem(LogLevel loglevel, String msg) {
			mLevel = loglevel;
			mMessage = msg;
		}


		public LogItem(LogLevel loglevel, int ressourceId) {
			mRessourceId =ressourceId;
			mLevel = loglevel;
		}

		public String getString(Context c) {
            try {
			if(mMessage !=null) {
				return mMessage;
			} else {
				if(c!=null) {
					if(mRessourceId==R.string.mobile_info)
						return getMobileInfoString(c);
					if(mArgs == null)
						return c.getString(mRessourceId);
					else
						return c.getString(mRessourceId,mArgs);
				} else {
					String str = String.format(Locale.ENGLISH,"Log (no context) resid %d", mRessourceId);
					if(mArgs !=null)
						for(Object o:mArgs)
							str += "|" +  o.toString();

					return str;
				}
			}
            } catch (UnknownFormatConversionException e) {
                if (c != null)
                    throw new UnknownFormatConversionException(e.getLocalizedMessage() + getString(null));
                else
                    throw e;
            } catch (java.util.FormatFlagsConversionMismatchException e) {
                if (c != null)
                    throw new FormatFlagsConversionMismatchException(e.getLocalizedMessage() + getString(null),e.getConversion());
                else
                    throw e;
            }

		}

        public LogLevel getLogLevel()
        {
            return mLevel;
        }

        // The lint is wrong here
		@SuppressLint("StringFormatMatches")
		private String getMobileInfoString(Context c) {
			c.getPackageManager();
			String apksign="error getting package signature";

			String version="error getting version";
			try {
				Signature raw = c.getPackageManager().getPackageInfo(c.getPackageName(), PackageManager.GET_SIGNATURES).signatures[0];
				CertificateFactory cf = CertificateFactory.getInstance("X.509");
				X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(raw.toByteArray()));
				MessageDigest md = MessageDigest.getInstance("SHA-1");
				byte[] der = cert.getEncoded();
				md.update(der);
				byte[] digest = md.digest();

				if (Arrays.equals(digest, officalkey))
					apksign = c.getString(R.string.official_build);
				else if (Arrays.equals(digest, officaldebugkey))
					apksign = c.getString(R.string.debug_build);
				else if (Arrays.equals(digest, amazonkey))
					apksign = "amazon version";
				else if (Arrays.equals(digest, fdroidkey))
                    apksign = "F-Droid built and signed version";
                else
					apksign = c.getString(R.string.built_by,cert.getSubjectX500Principal().getName());

				PackageInfo packageinfo = c.getPackageManager().getPackageInfo(c.getPackageName(), 0);
				version = packageinfo.versionName;

			} catch (NameNotFoundException e) {
			} catch (CertificateException e) {
			} catch (NoSuchAlgorithmException e) {
			}

			Object[] argsext = Arrays.copyOf(mArgs, mArgs.length+2);
			argsext[argsext.length-1]=apksign;
			argsext[argsext.length-2]=version;

			return c.getString(R.string.mobile_info_extended, argsext);

		}

		public long getLogtime() {
			return logtime;
		}


        public int getVerbosityLevel() {
            if (mVerbosityLevel==-1) {
                // Hack:
                // For message not from OpenVPN, report the status level as log level
                return mLevel.getInt();
            }
            return mVerbosityLevel;
        }
    }



	public interface LogListener {
		void newLog(LogItem logItem);
	}

	public interface StateListener {
		void updateState(String state, String logmessage, int localizedResId, ConnectionStatus level);
	}

	public interface ByteCountListener {
		void updateByteCount(long in, long out, long diffIn, long diffOut);
	}

	public synchronized static void logMessage(LogLevel level,String prefix, String message)
	{
		newLogItem(new LogItem(level, prefix + message));

	}

	public synchronized static void clearLog() {
		logbuffer.clear();
		logInformation();
	}

	private static void logInformation() {
		logInfo(R.string.mobile_info,Build.MODEL, Build.BOARD,Build.BRAND,Build.VERSION.SDK_INT);
	}

	public synchronized static void addLogListener(LogListener ll){
		logListener.add(ll);
	}

	public synchronized static void removeLogListener(LogListener ll) {
		logListener.remove(ll);
	}

	public synchronized static void addByteCountListener(ByteCountListener bcl) {
		bcl.updateByteCount(mlastByteCount[0],	mlastByteCount[1], mlastByteCount[2], mlastByteCount[3]);
		byteCountListener.add(bcl);
	}	

	public synchronized static void removeByteCountListener(ByteCountListener bcl) {
		byteCountListener.remove(bcl);
	}


	public synchronized static void addStateListener(StateListener sl){
		if(!stateListener.contains(sl)){
			stateListener.add(sl);
			if(mLaststate!=null)
				sl.updateState(mLaststate, mLaststatemsg, mLastStateresid, mLastLevel);
		}
	}	

	private static int getLocalizedState(String state){
		if (state.equals("CONNECTING")) 
			return R.string.state_connecting;
		else if (state.equals("WAIT"))
			return R.string.state_wait;
		else if (state.equals("AUTH"))
			return R.string.state_auth;
		else if (state.equals("GET_CONFIG"))
			return R.string.state_get_config;
		else if (state.equals("ASSIGN_IP"))
			return R.string.state_assign_ip;
		else if (state.equals("ADD_ROUTES"))
			return R.string.state_add_routes;
		else if (state.equals("CONNECTED"))
			return R.string.state_connected;
		else if (state.equals("DISCONNECTED"))
			return R.string.state_disconnected;
		else if (state.equals("RECONNECTING"))
			return R.string.state_reconnecting;
		else if (state.equals("EXITING"))
			return R.string.state_exiting;
		else if (state.equals("RESOLVE"))
			return R.string.state_resolve;
		else if (state.equals("TCP_CONNECT"))
			return R.string.state_tcp_connect;
		else
			return R.string.unknown_state;

	}

    public static void updateStatePause(OpenVPNManagement.pauseReason pauseReason) {
        switch (pauseReason) {
            case noNetwork:
                VpnStatus.updateStateString("NONETWORK", "", R.string.state_nonetwork, ConnectionStatus.LEVEL_NONETWORK);
                break;
            case screenOff:
                VpnStatus.updateStateString("SCREENOFF", "", R.string.state_screenoff, ConnectionStatus.LEVEL_VPNPAUSED);
                break;
            case userPause:
                VpnStatus.updateStateString("USERPAUSE", "", R.string.state_userpause, ConnectionStatus.LEVEL_VPNPAUSED);
                break;
        }

    }

    private static ConnectionStatus getLevel(String state){
		String[] noreplyet = {"CONNECTING","WAIT", "RECONNECTING", "RESOLVE", "TCP_CONNECT"}; 
		String[] reply = {"AUTH","GET_CONFIG","ASSIGN_IP","ADD_ROUTES"};
		String[] connected = {"CONNECTED"};
		String[] notconnected = {"DISCONNECTED", "EXITING"};

		for(String x:noreplyet)
			if(state.equals(x))
				return ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET;

		for(String x:reply)
			if(state.equals(x))
				return ConnectionStatus.LEVEL_CONNECTING_SERVER_REPLIED;

		for(String x:connected)
			if(state.equals(x))
				return ConnectionStatus.LEVEL_CONNECTED;

		for(String x:notconnected)
			if(state.equals(x))
				return ConnectionStatus.LEVEL_NOTCONNECTED;

		return ConnectionStatus.UNKNOWN_LEVEL;

	}




	public synchronized static void removeStateListener(StateListener sl) {
		stateListener.remove(sl);
	}


	synchronized public static LogItem[] getlogbuffer() {

		// The stoned way of java to return an array from a vector
		// brought to you by eclipse auto complete
		return logbuffer.toArray(new LogItem[logbuffer.size()]);

	}

	public static void updateStateString (String state, String msg) {
		int rid = getLocalizedState(state);
		ConnectionStatus level = getLevel(state);
		updateStateString(state, msg, rid, level);
	}

	public synchronized static void updateStateString(String state, String msg, int resid, ConnectionStatus level) {
        // Workound for OpenVPN doing AUTH and wait and being connected
        // Simply ignore these state
        if (mLastLevel == ConnectionStatus.LEVEL_CONNECTED &&
                (state.equals("WAIT") || state.equals("AUTH")))
        {
            newLogItem(new LogItem((LogLevel.DEBUG), String.format("Ignoring OpenVPN Status in CONNECTED state (%s->%s): %s",state,level.toString(),msg)));
            return;
        }

        mLaststate= state;
		mLaststatemsg = msg;
		mLastStateresid = resid;
		mLastLevel = level;



        for (StateListener sl : stateListener) {
			sl.updateState(state,msg,resid,level);
		}
        //newLogItem(new LogItem((LogLevel.DEBUG), String.format("New OpenVPN Status (%s->%s): %s",state,level.toString(),msg)));
	}

	public static void logInfo(String message) {
		newLogItem(new LogItem(LogLevel.INFO, message));
	}

	public static void logInfo(int resourceId, Object... args) {
		newLogItem(new LogItem(LogLevel.INFO, resourceId, args));
	}

    public static void logDebug(int resourceId, Object... args) {
        newLogItem(new LogItem(LogLevel.DEBUG, resourceId, args));
    }


    private synchronized static void newLogItem(LogItem logItem) {
		logbuffer.addLast(logItem);
		if(logbuffer.size()>MAXLOGENTRIES)
			logbuffer.removeFirst();

		for (LogListener ll : logListener) {
			ll.newLog(logItem);
		}
	}

	public static void logError(String msg) {
		newLogItem(new LogItem(LogLevel.ERROR, msg));

	}

    public static void logWarning(int resourceId, Object... args) {
        newLogItem(new LogItem(LogLevel.WARNING, resourceId, args));
    }

    public static void logWarning(String msg) {
        newLogItem(new LogItem(LogLevel.WARNING, msg));
    }


    public static void logError(int resourceId) {
		newLogItem(new LogItem(LogLevel.ERROR, resourceId));
	}
	public static void logError(int resourceId, Object... args) {
		newLogItem(new LogItem(LogLevel.ERROR, resourceId, args));
	}

    public static void logMessageOpenVPN(LogLevel level, int ovpnlevel, String message) {
        newLogItem(new LogItem(level, ovpnlevel, message));

    }


    public static synchronized void updateByteCount(long in, long out) {
		long lastIn = mlastByteCount[0];
		long lastOut = mlastByteCount[1];
		long diffIn = mlastByteCount[2] = in - lastIn;
		long diffOut = mlastByteCount[3] = out - lastOut;
		
		

		mlastByteCount = new long[] {in,out,diffIn,diffOut};
		for(ByteCountListener bcl:byteCountListener){
			bcl.updateByteCount(in, out, diffIn,diffOut);
		}
	}



} public class LocalBinder extends Binder {
    public OpenVpnService getService() {
        // Return this instance of LocalService so clients can call public methods
        return OpenVpnService.this;
    }
}   public enum ConnectionStatus {
      LEVEL_CONNECTED,
      LEVEL_VPNPAUSED,
      LEVEL_CONNECTING_SERVER_REPLIED,
      LEVEL_CONNECTING_NO_SERVER_REPLY_YET,
      LEVEL_NONETWORK,
LEVEL_NOTCONNECTED,
LEVEL_AUTH_FAILED,
LEVEL_WAITING_FOR_USER_INPUT,
UNKNOWN_LEVEL
  } public static class LogItem implements Parcelable {


	private Object [] mArgs = null;
	private String mMessage = null;
	private int mRessourceId;
	// Default log priority
	LogLevel mLevel = LogLevel.INFO;
	private long logtime = System.currentTimeMillis();
       private int mVerbosityLevel = -1;

	private LogItem(int ressourceId, Object[] args) {
		mRessourceId = ressourceId;
		mArgs = args;
	}

       public LogItem(LogLevel level, int verblevel, String message) {
           mMessage=message;
           mLevel = level;
           mVerbosityLevel = verblevel;
       }

       @Override
	public int describeContents() {
		return 0;
	}


       @Override
       public void writeToParcel(Parcel dest, int flags) {
           dest.writeArray(mArgs);
           dest.writeString(mMessage);
           dest.writeInt(mRessourceId);
           dest.writeInt(mLevel.getInt());
           dest.writeInt(mVerbosityLevel);

           dest.writeLong(logtime);
       }

	public LogItem(Parcel in) {
		mArgs = in.readArray(Object.class.getClassLoader());
		mMessage = in.readString();
		mRessourceId = in.readInt();
		mLevel = LogLevel.getEnumByValue(in.readInt());
           mVerbosityLevel = in.readInt();
		logtime = in.readLong();
	}

	public static final Parcelable.Creator<LogItem> CREATOR
	= new Parcelable.Creator<LogItem>() {
		public LogItem createFromParcel(Parcel in) {
			return new LogItem(in);
		}

		public LogItem[] newArray(int size) {
			return new LogItem[size];
		}
	};

       public LogItem(LogLevel loglevel,int ressourceId, Object... args) {
           mRessourceId = ressourceId;
           mArgs =args;
           mLevel = loglevel;
       }


	public LogItem(LogLevel loglevel, String msg) {
		mLevel = loglevel;
		mMessage = msg;
	}


	public LogItem(LogLevel loglevel, int ressourceId) {
		mRessourceId =ressourceId;
		mLevel = loglevel;
	}

	public String getString(Context c) {
           try {
		if(mMessage !=null) {
			return mMessage;
		} else {
			if(c!=null) {
				if(mRessourceId==R.string.mobile_info)
					return getMobileInfoString(c);
				if(mArgs == null)
					return c.getString(mRessourceId);
				else
					return c.getString(mRessourceId,mArgs);
			} else {
				String str = String.format(Locale.ENGLISH,"Log (no context) resid %d", mRessourceId);
				if(mArgs !=null)
					for(Object o:mArgs)
						str += "|" +  o.toString();

				return str;
			}
		}
           } catch (UnknownFormatConversionException e) {
               if (c != null)
                   throw new UnknownFormatConversionException(e.getLocalizedMessage() + getString(null));
               else
                   throw e;
           } catch (java.util.FormatFlagsConversionMismatchException e) {
               if (c != null)
                   throw new FormatFlagsConversionMismatchException(e.getLocalizedMessage() + getString(null),e.getConversion());
               else
                   throw e;
           }

	}

       public LogLevel getLogLevel()
       {
           return mLevel;
       }

       // The lint is wrong here
	@SuppressLint("StringFormatMatches")
	private String getMobileInfoString(Context c) {
		c.getPackageManager();
		String apksign="error getting package signature";

		String version="error getting version";
		try {
			Signature raw = c.getPackageManager().getPackageInfo(c.getPackageName(), PackageManager.GET_SIGNATURES).signatures[0];
			CertificateFactory cf = CertificateFactory.getInstance("X.509");
			X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(raw.toByteArray()));
			MessageDigest md = MessageDigest.getInstance("SHA-1");
			byte[] der = cert.getEncoded();
			md.update(der);
			byte[] digest = md.digest();

			if (Arrays.equals(digest, officalkey))
				apksign = c.getString(R.string.official_build);
			else if (Arrays.equals(digest, officaldebugkey))
				apksign = c.getString(R.string.debug_build);
			else if (Arrays.equals(digest, amazonkey))
				apksign = "amazon version";
			else if (Arrays.equals(digest, fdroidkey))
                   apksign = "F-Droid built and signed version";
               else
				apksign = c.getString(R.string.built_by,cert.getSubjectX500Principal().getName());

			PackageInfo packageinfo = c.getPackageManager().getPackageInfo(c.getPackageName(), 0);
			version = packageinfo.versionName;

		} catch (NameNotFoundException e) {
		} catch (CertificateException e) {
		} catch (NoSuchAlgorithmException e) {
		}

		Object[] argsext = Arrays.copyOf(mArgs, mArgs.length+2);
		argsext[argsext.length-1]=apksign;
		argsext[argsext.length-2]=version;

		return c.getString(R.string.mobile_info_extended, argsext);

	}

	public long getLogtime() {
		return logtime;
	}


       public int getVerbosityLevel() {
           if (mVerbosityLevel==-1) {
               // Hack:
               // For message not from OpenVPN, report the status level as log level
               return mLevel.getInt();
           }
           return mVerbosityLevel;
       }
   } public interface LogListener {
	void newLog(LogItem logItem);
} public interface StateListener {
	void updateState(String state, String logmessage, int localizedResId, ConnectionStatus level);
} public class LoggingConfig {
    /** Black phone or Google billing build **/
    public final static boolean LOGGING = @CONFIG.LOGGING@;
} public interface VPNImplementation {
    public final int PREPARE_VPN_SERVICE = 213;
    
    public static final String LAUNCH_THIS = "launch this";
    
    // Shared Preferences
    public static final String KEY_BANDWIDTH_LIMIT = "bandwidth limit"; // In megabytes
    public static final String KEY_BANDWIDTH_USED = "bandwidth used"; // In megabytes
    public static final String VPN_PREFS = "vpn preferences"; // SharedPreferences file name.
    public static final String KEY_GETTING_PERMISSION = "getting permission"; // semaphore to serialize the approval process.
    public static final String KEY_PERMISSION_REVOKED = "permission revoked"; // If user wants us all the way off.
    public static final String PROTECTED_MESSAGE = "VPN_CONNECTED_MESSAGE"; // Message to show when vpn is connected.
    
    // default values
    public static final float DEFAULT_BANDWIDTH_LIMIT = 10;
    // Identifies our status notification so we can update it. Can be any integer we like.
    public static final int NOTIFICATION_ID = 415;
    
    /**
     * Connection states and support for listeners to state changes.
     */
    public enum CONNECTION_STATE { DISABLED, CONNECTING, CONNECTED, DISCONNECTING }
    public interface ConnectionListener {
        public void stateChanged(CONNECTION_STATE newState);
    }
    public void addStateListener(ConnectionListener sl);
    public void removeStateListener(ConnectionListener sl);
    public void fireStateChanged(); // Normally protected method.
    public CONNECTION_STATE getConnectionState();

    /**
     * Attempts to connect to start the VPN service.
     * This requires the user to approve this action, at least initially.
     * For new connections the user will be presented with a system dialog.
     * When the user agrees, the given Activity's onActivityResult() method is called,
     * and will be passed the above PREPARE_VPN_SERVICE request code.
     * Assuming the result code is RESULT_OK, the onActivityResult() method
     * must call connect() once more to complete the connection.
     */
    public void connect(String username, String password, String aGateway, int[] aPorts, String aCrtCert, String aTaCert, String aClientCert, String aClientKey, String aConfigFile);
    public void disconnect();
    
    /*
     * Called when the connectivity receiver detects that the network has changed. It
     * is the responsibility of the provider to manage the transition to the new
     * network.
     */
    public void networkChanged();
    
    /**
     * Stops VPN provider. Stops any background servicers started by provider
     * and deregisters any observers.
     */
    public void stopProvider();
    
    // TODO Implement proper message handling back to UX
    public void init(Context context);
    public void endit(Context context);
}
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.util.Vector;
import de.blinkt.openvpn.VpnProfile;
import de.blinkt.openvpn.core.ConfigParser;
import de.blinkt.openvpn.core.ConfigParser.ConfigParseError;
import de.blinkt.openvpn.core.OpenVpnService;
import de.blinkt.openvpn.core.ProfileManager;
import de.blinkt.openvpn.core.VPNLaunchHelper;
import de.blinkt.openvpn.core.VpnStatus;
import de.blinkt.openvpn.core.OpenVpnService.LocalBinder;
import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus;
import de.blinkt.openvpn.core.VpnStatus.LogItem;
import de.blinkt.openvpn.core.VpnStatus.LogListener;
import de.blinkt.openvpn.core.VpnStatus.StateListener;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.net.VpnService;
import android.os.IBinder;
import android.util.Log;
import me.disconnect.securefi.engine.LoggingConfig;
import me.disconnect.securefi.engine.VPNImplementation;
package me.disconnect.securefi.openvpnlib;



public class OpenVPNImplementation implements VPNImplementation, StateListener {

	private Vector<ConnectionListener> mConnectionListeners = new Vector<ConnectionListener>();
	private CONNECTION_STATE mLastConnectionState = null;
	private OpenVpnService mService;
	private Context mContext;
	private VpnProfile mVp;
	
	private static LogListener mLogListener;
	
	public OpenVPNImplementation(Context aContext){
		mContext = aContext.getApplicationContext();
		
		if ( LoggingConfig.LOGGING ){
			if ( mLogListener == null ){
				mLogListener = new LogListener(){
					@Override
					public void newLog(LogItem logItem) {
						Log.d("OPEN_VPN", logItem.getString(mContext));
					}
				};
			} else {
				VpnStatus.removeLogListener(mLogListener);
			}
			
			// Add logging to logcat
			VpnStatus.addLogListener(mLogListener);		
		}
	}
	
	@Override
	public void updateState(String state, String logmessage,
			int localizedResId, ConnectionStatus level) {