From 89545dcbcbd867a407c5f9c30a7f17adc76b748a Mon Sep 17 00:00:00 2001 From: Arne Schwabe Date: Sun, 13 May 2018 17:14:35 +0200 Subject: Fall back to normal proxy options in the config in the presence of extra http options, closes #869 --- .../main/java/de/blinkt/openvpn/VpnProfile.java | 202 ++++++------ .../java/de/blinkt/openvpn/core/ConfigParser.java | 338 ++++++++++----------- .../java/de/blinkt/openvpn/core/Connection.java | 10 +- main/src/main/res/values/strings.xml | 1 + .../blinkt/openvpn/core/TestConfigGenerator.java | 1 + .../de/blinkt/openvpn/core/TestConfigParser.java | 74 +++++ 6 files changed, 359 insertions(+), 267 deletions(-) diff --git a/main/src/main/java/de/blinkt/openvpn/VpnProfile.java b/main/src/main/java/de/blinkt/openvpn/VpnProfile.java index fcf3c20c..4e391e2e 100644 --- a/main/src/main/java/de/blinkt/openvpn/VpnProfile.java +++ b/main/src/main/java/de/blinkt/openvpn/VpnProfile.java @@ -287,57 +287,57 @@ public class VpnProfile implements Serializable, Cloneable { public String getConfigFile(Context context, boolean configForOvpn3) { File cacheDir = context.getCacheDir(); - String cfg = ""; + StringBuilder cfg = new StringBuilder(); if (!configForOvpn3) { // Enable management interface - cfg += "# Config for OpenVPN 2.x\n"; - cfg += "# Enables connection to GUI\n"; - cfg += "management "; + cfg.append("# Config for OpenVPN 2.x\n"); + cfg.append("# Enables connection to GUI\n"); + cfg.append("management "); - cfg += cacheDir.getAbsolutePath() + "/" + "mgmtsocket"; - cfg += " unix\n"; - cfg += "management-client\n"; + cfg.append(cacheDir.getAbsolutePath()).append("/").append("mgmtsocket"); + cfg.append(" unix\n"); + cfg.append("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"; + cfg.append("management-query-passwords\n"); + cfg.append("management-hold\n\n"); - cfg += String.format("setenv IV_GUI_VER %s \n", openVpnEscape(getVersionEnvString(context))); + cfg.append(String.format("setenv IV_GUI_VER %s \n", openVpnEscape(getVersionEnvString(context)))); String versionString = getPlatformVersionEnvString(); - cfg += String.format("setenv IV_PLAT_VER %s\n", openVpnEscape(versionString)); + cfg.append(String.format("setenv IV_PLAT_VER %s\n", openVpnEscape(versionString))); } else { - cfg += "# Config for OpenVPN 3 C++\n"; + cfg.append("# Config for OpenVPN 3 C++\n"); } - cfg += "machine-readable-output\n"; + cfg.append("machine-readable-output\n"); if (!mIsOpenVPN22) - cfg += "allow-recursive-routing\n"; + cfg.append("allow-recursive-routing\n"); // Users are confused by warnings that are misleading... - cfg += "ifconfig-nowarn\n"; + cfg.append("ifconfig-nowarn\n"); boolean useTLSClient = (mAuthenticationType != TYPE_STATICKEYS); if (useTLSClient && mUsePull) - cfg += "client\n"; + cfg.append("client\n"); else if (mUsePull) - cfg += "pull\n"; + cfg.append("pull\n"); else if (useTLSClient) - cfg += "tls-client\n"; + cfg.append("tls-client\n"); //cfg += "verb " + mVerb + "\n"; - cfg += "verb " + MAXLOGLEVEL + "\n"; + cfg.append("verb " + MAXLOGLEVEL + "\n"); if (mConnectRetryMax == null) { mConnectRetryMax = "-1"; } if (!mConnectRetryMax.equals("-1")) - cfg += "connect-retry-max " + mConnectRetryMax + "\n"; + cfg.append("connect-retry-max ").append(mConnectRetryMax).append("\n"); if (TextUtils.isEmpty(mConnectRetry)) mConnectRetry = "2"; @@ -347,34 +347,34 @@ public class VpnProfile implements Serializable, Cloneable { if (!mIsOpenVPN22) - cfg += "connect-retry " + mConnectRetry + " " + mConnectRetryMaxTime + "\n"; + cfg.append("connect-retry ").append(mConnectRetry).append(" ").append(mConnectRetryMaxTime).append("\n"); else if (mIsOpenVPN22 && !mUseUdp) - cfg += "connect-retry " + mConnectRetry + "\n"; + cfg.append("connect-retry ").append(mConnectRetry).append("\n"); - cfg += "resolv-retry 60\n"; + cfg.append("resolv-retry 60\n"); // We cannot use anything else than tun - cfg += "dev tun\n"; + cfg.append("dev tun\n"); boolean canUsePlainRemotes = true; if (mConnections.length == 1) { - cfg += mConnections[0].getConnectionBlock(configForOvpn3); + cfg.append(mConnections[0].getConnectionBlock(configForOvpn3)); } else { for (Connection conn : mConnections) { canUsePlainRemotes = canUsePlainRemotes && conn.isOnlyRemote(); } if (mRemoteRandom) - cfg += "remote-random\n"; + cfg.append("remote-random\n"); if (canUsePlainRemotes) { for (Connection conn : mConnections) { if (conn.mEnabled) { - cfg += conn.getConnectionBlock(configForOvpn3); + cfg.append(conn.getConnectionBlock(configForOvpn3)); } } } @@ -383,95 +383,95 @@ public class VpnProfile implements Serializable, Cloneable { switch (mAuthenticationType) { case VpnProfile.TYPE_USERPASS_CERTIFICATES: - cfg += "auth-user-pass\n"; + cfg.append("auth-user-pass\n"); case VpnProfile.TYPE_CERTIFICATES: // Ca - cfg += insertFileData("ca", mCaFilename); + cfg.append(insertFileData("ca", mCaFilename)); // Client Cert + Key - cfg += insertFileData("key", mClientKeyFilename); - cfg += insertFileData("cert", mClientCertFilename); + cfg.append(insertFileData("key", mClientKeyFilename)); + cfg.append(insertFileData("cert", mClientCertFilename)); break; case VpnProfile.TYPE_USERPASS_PKCS12: - cfg += "auth-user-pass\n"; + cfg.append("auth-user-pass\n"); case VpnProfile.TYPE_PKCS12: - cfg += insertFileData("pkcs12", mPKCS12Filename); + cfg.append(insertFileData("pkcs12", mPKCS12Filename)); break; case VpnProfile.TYPE_USERPASS_KEYSTORE: - cfg += "auth-user-pass\n"; + cfg.append("auth-user-pass\n"); case VpnProfile.TYPE_KEYSTORE: if (!configForOvpn3) { String[] ks = getKeyStoreCertificates(context); - cfg += "### From Keystore ####\n"; + cfg.append("### From Keystore ####\n"); if (ks != null) { - cfg += "\n" + ks[0] + "\n\n"; + cfg.append("\n").append(ks[0]).append("\n\n"); if (ks[1] != null) - cfg += "\n" + ks[1] + "\n\n"; - cfg += "\n" + ks[2] + "\n\n"; - cfg += "management-external-key\n"; + cfg.append("\n").append(ks[1]).append("\n\n"); + cfg.append("\n").append(ks[2]).append("\n\n"); + cfg.append("management-external-key\n"); } else { - cfg += context.getString(R.string.keychain_access) + "\n"; + cfg.append(context.getString(R.string.keychain_access)).append("\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"; + cfg.append(context.getString(R.string.jelly_keystore_alphanumeric_bug)).append("\n"); } } break; case VpnProfile.TYPE_USERPASS: - cfg += "auth-user-pass\n"; - cfg += insertFileData("ca", mCaFilename); + cfg.append("auth-user-pass\n"); + cfg.append(insertFileData("ca", mCaFilename)); if (configForOvpn3) { // OpenVPN 3 needs to be told that a client certificate is not required - cfg += "client-cert-not-required\n"; + cfg.append("client-cert-not-required\n"); } } if (isUserPWAuth()) { if (mAuthRetry == AUTH_RETRY_NOINTERACT) - cfg += "auth-retry nointeract\n"; + cfg.append("auth-retry nointeract\n"); } if (!TextUtils.isEmpty(mCrlFilename)) - cfg += insertFileData("crl-verify", mCrlFilename); + cfg.append(insertFileData("crl-verify", mCrlFilename)); if (mUseLzo) { - cfg += "comp-lzo\n"; + cfg.append("comp-lzo\n"); } if (mUseTLSAuth) { boolean useTlsCrypt = mTLSAuthDirection.equals("tls-crypt"); if (mAuthenticationType == TYPE_STATICKEYS) - cfg += insertFileData("secret", mTLSAuthFilename); + cfg.append(insertFileData("secret", mTLSAuthFilename)); else if (useTlsCrypt) - cfg += insertFileData("tls-crypt", mTLSAuthFilename); + cfg.append(insertFileData("tls-crypt", mTLSAuthFilename)); else - cfg += insertFileData("tls-auth", mTLSAuthFilename); + cfg.append(insertFileData("tls-auth", mTLSAuthFilename)); if (!TextUtils.isEmpty(mTLSAuthDirection) && !useTlsCrypt) { - cfg += "key-direction "; - cfg += mTLSAuthDirection; - cfg += "\n"; + cfg.append("key-direction "); + cfg.append(mTLSAuthDirection); + cfg.append("\n"); } } if (!mUsePull) { if (!TextUtils.isEmpty(mIPv4Address)) - cfg += "ifconfig " + cidrToIPAndNetmask(mIPv4Address) + "\n"; + cfg.append("ifconfig ").append(cidrToIPAndNetmask(mIPv4Address)).append("\n"); if (!TextUtils.isEmpty(mIPv6Address)) { // Use our own ip as gateway since we ignore it anyway String fakegw = mIPv6Address.split("/", 2)[0]; - cfg += "ifconfig-ipv6 " + mIPv6Address + " " + fakegw + "\n"; + cfg.append("ifconfig-ipv6 ").append(mIPv6Address).append(" ").append(fakegw).append("\n"); } } if (mUsePull && mRoutenopull) - cfg += "route-nopull\n"; + cfg.append("route-nopull\n"); String routes = ""; @@ -489,129 +489,129 @@ public class VpnProfile implements Serializable, Cloneable { if (mUseDefaultRoutev6) - cfg += "route-ipv6 ::/0\n"; + cfg.append("route-ipv6 ::/0\n"); else for (String route : getCustomRoutesv6(mCustomRoutesv6)) { routes += "route-ipv6 " + route + "\n"; } - cfg += routes; + cfg.append(routes); if (mOverrideDNS || !mUsePull) { if (!TextUtils.isEmpty(mDNS1)) { - cfg += "dhcp-option DNS " + mDNS1 + "\n"; + cfg.append("dhcp-option DNS ").append(mDNS1).append("\n"); } if (!TextUtils.isEmpty(mDNS2)) { - cfg += "dhcp-option DNS " + mDNS2 + "\n"; + cfg.append("dhcp-option DNS ").append(mDNS2).append("\n"); } if (!TextUtils.isEmpty(mSearchDomain)) - cfg += "dhcp-option DOMAIN " + mSearchDomain + "\n"; + cfg.append("dhcp-option DOMAIN ").append(mSearchDomain).append("\n"); } if (mMssFix != 0) { if (mMssFix != 1450) { - cfg += String.format(Locale.US, "mssfix %d\n", mMssFix); + cfg.append(String.format(Locale.US, "mssfix %d\n", mMssFix)); } else - cfg += "mssfix\n"; + cfg.append("mssfix\n"); } if (mTunMtu >= 48 && mTunMtu != 1500) { - cfg += String.format(Locale.US, "tun-mtu %d\n", mTunMtu); + cfg.append(String.format(Locale.US, "tun-mtu %d\n", mTunMtu)); } if (mNobind) - cfg += "nobind\n"; + cfg.append("nobind\n"); // Authentication if (mAuthenticationType != TYPE_STATICKEYS) { if (mCheckRemoteCN) { if (mRemoteCN == null || mRemoteCN.equals("")) - cfg += "verify-x509-name " + openVpnEscape(mConnections[0].mServerName) + " name\n"; + cfg.append("verify-x509-name ").append(openVpnEscape(mConnections[0].mServerName)).append(" name\n"); else switch (mX509AuthType) { // 2.2 style x509 checks case X509_VERIFY_TLSREMOTE_COMPAT_NOREMAPPING: - cfg += "compat-names no-remapping\n"; + cfg.append("compat-names no-remapping\n"); case X509_VERIFY_TLSREMOTE: - cfg += "tls-remote " + openVpnEscape(mRemoteCN) + "\n"; + cfg.append("tls-remote ").append(openVpnEscape(mRemoteCN)).append("\n"); break; case X509_VERIFY_TLSREMOTE_RDN: - cfg += "verify-x509-name " + openVpnEscape(mRemoteCN) + " name\n"; + cfg.append("verify-x509-name ").append(openVpnEscape(mRemoteCN)).append(" name\n"); break; case X509_VERIFY_TLSREMOTE_RDN_PREFIX: - cfg += "verify-x509-name " + openVpnEscape(mRemoteCN) + " name-prefix\n"; + cfg.append("verify-x509-name ").append(openVpnEscape(mRemoteCN)).append(" name-prefix\n"); break; case X509_VERIFY_TLSREMOTE_DN: - cfg += "verify-x509-name " + openVpnEscape(mRemoteCN) + "\n"; + cfg.append("verify-x509-name ").append(openVpnEscape(mRemoteCN)).append("\n"); break; } if (!TextUtils.isEmpty(mx509UsernameField)) - cfg += "x509-username-field " + openVpnEscape(mx509UsernameField) + "\n"; + cfg.append("x509-username-field ").append(openVpnEscape(mx509UsernameField)).append("\n"); } if (mExpectTLSCert) - cfg += "remote-cert-tls server\n"; + cfg.append("remote-cert-tls server\n"); } if (!TextUtils.isEmpty(mCipher)) { - cfg += "cipher " + mCipher + "\n"; + cfg.append("cipher ").append(mCipher).append("\n"); } if (!TextUtils.isEmpty(mAuth)) { - cfg += "auth " + mAuth + "\n"; + cfg.append("auth ").append(mAuth).append("\n"); } // Obscure Settings dialog if (mUseRandomHostname) - cfg += "#my favorite options :)\nremote-random-hostname\n"; + cfg.append("#my favorite options :)\nremote-random-hostname\n"); if (mUseFloat) - cfg += "float\n"; + cfg.append("float\n"); if (mPersistTun) { - cfg += "persist-tun\n"; - cfg += "# persist-tun also enables pre resolving to avoid DNS resolve problem\n"; + cfg.append("persist-tun\n"); + cfg.append("# persist-tun also enables pre resolving to avoid DNS resolve problem\n"); if (!mIsOpenVPN22) - cfg += "preresolve\n"; + cfg.append("preresolve\n"); } if (mPushPeerInfo) - cfg += "push-peer-info\n"; + cfg.append("push-peer-info\n"); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); boolean usesystemproxy = prefs.getBoolean("usesystemproxy", true); - if (usesystemproxy && !mIsOpenVPN22 && !configForOvpn3) { - cfg += "# Use system proxy setting\n"; - cfg += "management-query-proxy\n"; + if (usesystemproxy && !mIsOpenVPN22 && !configForOvpn3 && !usesExtraProxyOptions()) { + cfg.append("# Use system proxy setting\n"); + cfg.append("management-query-proxy\n"); } if (mUseCustomConfig) { - cfg += "# Custom configuration options\n"; - cfg += "# You are on your on own here :)\n"; - cfg += mCustomConfigOptions; - cfg += "\n"; + cfg.append("# Custom configuration options\n"); + cfg.append("# You are on your on own here :)\n"); + cfg.append(mCustomConfigOptions); + cfg.append("\n"); } if (!canUsePlainRemotes) { - cfg += "# Connection Options are at the end to allow global options (and global custom options) to influence connection blocks\n"; + cfg.append("# Connection Options are at the end to allow global options (and global custom options) to influence connection blocks\n"); for (Connection conn : mConnections) { if (conn.mEnabled) { - cfg += "\n"; - cfg += conn.getConnectionBlock(configForOvpn3); - cfg += "\n"; + cfg.append("\n"); + cfg.append(conn.getConnectionBlock(configForOvpn3)); + cfg.append("\n"); } } } - return cfg; + return cfg.toString(); } public String getPlatformVersionEnvString() { @@ -977,9 +977,12 @@ public class VpnProfile implements Serializable, Cloneable { } } for (Connection c: mConnections) { - if (c.mProxyType == Connection.ProxyType.ORBOT) + if (c.mProxyType == Connection.ProxyType.ORBOT) { + if (usesExtraProxyOptions()) + return R.string.error_orbot_and_proxy_options; if (!OrbotHelper.checkTorReceier(context)) return R.string.no_orbotfound; + } } @@ -1175,6 +1178,17 @@ public class VpnProfile implements Serializable, Cloneable { } } + private boolean usesExtraProxyOptions() + { + if (mUseCustomConfig && mCustomConfigOptions !=null && mCustomConfigOptions.contains("http-proxy-option ")) + return true; + for (Connection c: mConnections) + if (c.usesExtraProxyOptions()) + return true; + + return false; + } + } diff --git a/main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java b/main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java index 10fcb12a..611f77d5 100644 --- a/main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java +++ b/main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java @@ -13,10 +13,7 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; import java.io.StringReader; -import java.util.Collections; -import java.util.HashMap; -import java.util.Locale; -import java.util.Vector; +import java.util.*; import de.blinkt.openvpn.VpnProfile; @@ -30,10 +27,126 @@ public class ConfigParser { public static final String CONVERTED_PROFILE = "converted Profile"; + final String[] unsupportedOptions = {"config", + "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", + "allow-recursive-routing", + "askpass", + "auth-nocache", + "up", + "down", + "route-up", + "ipchange", + "route-up", + "route-pre-down", + "auth-user-pass-verify", + "block-outside-dns", + "client-cert-not-required", + "dhcp-release", + "dhcp-renew", + "dh", + "group", + "ip-win32", + "ifconfig-nowarn", + "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", + "preresolve", + "plugin", + "machine-readable-output", + "persist-key", + "push", + "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", + }; + final String[][] ignoreOptionsWithArg = + { + {"setenv", "IV_GUI_VER"}, + {"setenv", "IV_OPENVPN_GUI_VERSION"}, + {"engine", "dynamic"}, + {"setenv", "CLIENT_CERT"}, + {"resolve-retry", "60"} + }; + 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", + "http-proxy-user-pass", + "explicit-exit-notify", + }; + private HashSet connectionOptionsSet = new HashSet<>(Arrays.asList(connectionOptions)); + private HashMap>> options = new HashMap<>(); private HashMap> meta = new HashMap>(); private String auth_user_pass_file; + 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]; + } + } + + static public void useEmbbedHttpAuth(Connection c, String inlinedata) { + String data = VpnProfile.getEmbeddedContent(inlinedata); + String[] parts = data.split("\n"); + if (parts.length >= 2) { + c.mProxyAuthUser = parts[0]; + c.mProxyAuthPassword = parts[1]; + c.mUseProxyAuth = true; + } + } + public void parseConfig(Reader reader) throws IOException, ConfigParseError { HashMap optionAliases = new HashMap<>(); @@ -77,7 +190,7 @@ public class ConfigParser { checkinlinefile(args, br); String optionname = args.get(0); - if (optionAliases.get(optionname)!=null) + if (optionAliases.get(optionname) != null) optionname = optionAliases.get(optionname); if (!options.containsKey(optionname)) { @@ -120,8 +233,8 @@ public class ConfigParser { } } while (true); - if(inlinefile.endsWith("\n")) - inlinefile = inlinefile.substring(0, inlinefile.length()-1); + if (inlinefile.endsWith("\n")) + inlinefile = inlinefile.substring(0, inlinefile.length() - 1); args.clear(); args.add(argname); @@ -134,11 +247,6 @@ public class ConfigParser { return auth_user_pass_file; } - 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: @@ -146,15 +254,6 @@ public class ConfigParser { } - public static 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 parseline(String line) throws ConfigParseError { Vector parameters = new Vector(); @@ -227,7 +326,7 @@ public class ConfigParser { backslash = false; } - /* store parameter character */ + /* store parameter character */ if (out != 0) { currentarg += out; } @@ -236,107 +335,6 @@ public class ConfigParser { return parameters; } - - final String[] unsupportedOptions = {"config", - "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", - "allow-recursive-routing", - "askpass", - "auth-nocache", - "up", - "down", - "route-up", - "ipchange", - "route-up", - "route-pre-down", - "auth-user-pass-verify", - "block-outside-dns", - "client-cert-not-required", - "dhcp-release", - "dhcp-renew", - "dh", - "group", - "ip-win32", - "ifconfig-nowarn", - "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", - "preresolve", - "plugin", - "machine-readable-output", - "persist-key", - "push", - "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", - }; - - final String[][] ignoreOptionsWithArg = - { - {"setenv", "IV_GUI_VER"}, - {"setenv", "IV_OPENVPN_GUI_VERSION"}, - {"engine", "dynamic"}, - {"setenv", "CLIENT_CERT"}, - {"resolve-retry","60"} - }; - - 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", - "http-proxy-user-pass", - "explicit-exit-notify", - }; - - // This method is far too long @SuppressWarnings("ConstantConditions") public VpnProfile convertProfile() throws ConfigParseError, IOException { @@ -406,8 +404,8 @@ public class ConfigParser { } Vector routeNoPull = getOption("route-nopull", 0, 0); - if (routeNoPull!=null) - np.mRoutenopull=true; + if (routeNoPull != null) + np.mRoutenopull = true; // Also recognize tls-auth [inline] direction ... Vector> tlsauthoptions = getAllOption("tls-auth", 1, 2); @@ -429,7 +427,7 @@ public class ConfigParser { np.mTLSAuthDirection = direction.get(1); Vector tlscrypt = getOption("tls-crypt", 1, 1); - if (tlscrypt!=null) { + if (tlscrypt != null) { np.mUseTLSAuth = true; np.mTLSAuthFilename = tlscrypt.get(1); np.mTLSAuthDirection = "tls-crypt"; @@ -481,7 +479,6 @@ public class ConfigParser { } - Vector mode = getOption("mode", 1, 1); if (mode != null) { if (!mode.get(1).equals("p2p")) @@ -595,9 +592,9 @@ public class ConfigParser { } - Vector x509usernamefield = getOption("x509-username-field", 1,1); - if (x509usernamefield!=null) { - np.mx509UsernameField = x509usernamefield.get(1); + Vector x509usernamefield = getOption("x509-username-field", 1, 1); + if (x509usernamefield != null) { + np.mx509UsernameField = x509usernamefield.get(1); } @@ -812,7 +809,7 @@ public class ConfigParser { } Vector httpproxyauthhttp = getOption("http-proxy-user-pass", 1, 1); - if (httpproxyauthhttp!=null) + if (httpproxyauthhttp != null) useEmbbedHttpAuth(conn, httpproxyauthhttp.get(1)); @@ -820,16 +817,21 @@ public class ConfigParser { Vector> remotes = getAllOption("remote", 1, 3); - // Assume that we need custom options if connectionDefault are set - if (connDefault != null) { - for (Vector> option : options.values()) { - - conn.mCustomConfiguration += getOptionStrings(option); + Vector optionsToRemove = new Vector<>(); + // Assume that we need custom options if connectionDefault are set or in the connection specific set + for (Map.Entry>> option : options.entrySet()) { + if (connDefault != null || connectionOptionsSet.contains(option.getKey())) { + conn.mCustomConfiguration += getOptionStrings(option.getValue()); + optionsToRemove.add(option.getKey()); } - if (!(conn.mCustomConfiguration == null || "".equals(conn.mCustomConfiguration.trim()))) - conn.mUseCustomConfig = true; } + for (String o: optionsToRemove) + options.remove(o); + + if (!(conn.mCustomConfiguration == null || "".equals(conn.mCustomConfiguration.trim()))) + conn.mUseCustomConfig = true; + // Make remotes empty to simplify code if (remotes == null) remotes = new Vector>(); @@ -854,6 +856,7 @@ public class ConfigParser { } i++; } + return Pair.create(conn, connections); } @@ -863,19 +866,19 @@ public class ConfigParser { boolean noIpv4 = false; if (defaultRoute) - for (Vector 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; - else if (redirect.get(i).equals("!ipv4")) - noIpv4=true; - else if (redirect.get(i).equals("ipv6")) - np.mUseDefaultRoutev6=true; - } + for (Vector 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; + else if (redirect.get(i).equals("!ipv4")) + noIpv4 = true; + else if (redirect.get(i).equals("ipv6")) + np.mUseDefaultRoutev6 = true; + } if (defaultRoute && !noIpv4) - np.mUseDefaultRoute=true; + np.mUseDefaultRoute = true; } private boolean isUdpProto(String proto) throws ConfigParseError { @@ -894,25 +897,6 @@ public class ConfigParser { 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]; - } - } - - static public void useEmbbedHttpAuth(Connection c, String inlinedata) { - String data = VpnProfile.getEmbeddedContent(inlinedata); - String[] parts = data.split("\n"); - if (parts.length >= 2) { - c.mProxyAuthUser = parts[0]; - c.mProxyAuthPassword = parts[1]; - c.mUseProxyAuth = true; - } - } - private void checkIgnoreAndInvalidOptions(VpnProfile np) throws ConfigParseError { for (String option : unsupportedOptions) if (options.containsKey(option)) @@ -937,7 +921,6 @@ public class ConfigParser { } } - boolean ignoreThisOption(Vector option) { for (String[] ignoreOption : ignoreOptionsWithArg) { @@ -975,7 +958,6 @@ public class ConfigParser { return custom; } - private void fixup(VpnProfile np) { if (np.mRemoteCN.equals(np.mServerName)) { np.mRemoteCN = ""; @@ -990,7 +972,6 @@ public class ConfigParser { return alloptions.lastElement(); } - private Vector> getAllOption(String option, int minarg, int maxarg) throws ConfigParseError { Vector> args = options.get(option); if (args == null) @@ -1007,6 +988,19 @@ public class ConfigParser { return args; } + enum linestate { + initial, + readin_single_quote, reading_quoted, reading_unquoted, done + } + + public static class ConfigParseError extends Exception { + private static final long serialVersionUID = -60L; + + public ConfigParseError(String msg) { + super(msg); + } + } + } diff --git a/main/src/main/java/de/blinkt/openvpn/core/Connection.java b/main/src/main/java/de/blinkt/openvpn/core/Connection.java index 5f24ecb4..df071894 100644 --- a/main/src/main/java/de/blinkt/openvpn/core/Connection.java +++ b/main/src/main/java/de/blinkt/openvpn/core/Connection.java @@ -54,12 +54,15 @@ public class Connection implements Serializable, Cloneable { cfg += String.format(Locale.US, " connect-timeout %d\n", mConnectTimeout); // OpenVPN 2.x manages proxy connection via management interface - if (isOpenVPN3 && mProxyType == ProxyType.HTTP) + if ((isOpenVPN3 || usesExtraProxyOptions()) && mProxyType == ProxyType.HTTP) { cfg+=String.format(Locale.US,"http-proxy %s %s\n", mProxyName, mProxyPort); if (mUseProxyAuth) cfg+=String.format(Locale.US, "\n%s\n%s\n\n", mProxyAuthUser, mProxyAuthPassword); } + if (usesExtraProxyOptions() && mProxyType == ProxyType.SOCKS5) { + cfg+=String.format(Locale.US,"socks-proxy %s %s\n", mProxyName, mProxyPort); + } if (!TextUtils.isEmpty(mCustomConfiguration) && mUseCustomConfig) { cfg += mCustomConfiguration; @@ -70,6 +73,11 @@ public class Connection implements Serializable, Cloneable { return cfg; } + public boolean usesExtraProxyOptions() { + return (mUseCustomConfig && mCustomConfiguration.contains("http-proxy-option ")); + } + + @Override public Connection clone() throws CloneNotSupportedException { return (Connection) super.clone(); diff --git a/main/src/main/res/values/strings.xml b/main/src/main/res/values/strings.xml index 2426e501..a7cd6a61 100755 --- a/main/src/main/res/values/strings.xml +++ b/main/src/main/res/values/strings.xml @@ -474,4 +474,5 @@ Remote API OpenVPN for Android supports two remote APIs, a sophisticated API using AIDL (remoteEXample in the git repository) and a simple one using Intents. <p>Examples using adb shell and the intents. Replace profilname with your profile name<p><p> adb shell am start-activity -a android.intent.action.MAIN de.blinkt.openvpn/.api.DisconnectVPN<p> adb shell am start-activity -a android.intent.action.MAIN -e de.blinkt.openvpn.api.profileName Blinkt de.blinkt.openvpn/.api.ConnectVPN Enable Proxy Authentication + Cannot use extra http-proxy-option statement and Orbot integration at the same timeO diff --git a/main/src/test/java/de/blinkt/openvpn/core/TestConfigGenerator.java b/main/src/test/java/de/blinkt/openvpn/core/TestConfigGenerator.java index 4fb14d2c..892a5807 100644 --- a/main/src/test/java/de/blinkt/openvpn/core/TestConfigGenerator.java +++ b/main/src/test/java/de/blinkt/openvpn/core/TestConfigGenerator.java @@ -65,4 +65,5 @@ public class TestConfigGenerator { } + } diff --git a/main/src/test/java/de/blinkt/openvpn/core/TestConfigParser.java b/main/src/test/java/de/blinkt/openvpn/core/TestConfigParser.java index 3e5c9895..f0047ac7 100644 --- a/main/src/test/java/de/blinkt/openvpn/core/TestConfigParser.java +++ b/main/src/test/java/de/blinkt/openvpn/core/TestConfigParser.java @@ -16,6 +16,7 @@ import org.robolectric.RuntimeEnvironment; import java.io.IOException; import java.io.StringReader; +import java.util.Arrays; import de.blinkt.openvpn.VpnProfile; import org.robolectric.annotation.Config; @@ -139,4 +140,77 @@ public class TestConfigParser { Assert.assertEquals(vp.mConnections[0].mProxyAuthPassword, "password34"); } + @Test + public void testConfigWithHttpProxyOptions() throws IOException, ConfigParser.ConfigParseError { + String proxyconf = "pull\n" + + "dev tun\n" + + "proto tcp-client\n" + + "cipher AES-128-CBC\n" + + "auth SHA1\n" + + "reneg-sec 0\n" + + "remote-cert-tls server\n" + + "tls-version-min 1.2 or-highest\n" + + "persist-tun\n" + + "nobind\n" + + "connect-retry 2 2\n" + + "dhcp-option DNS 1.1.1.1\n" + + "dhcp-option DNS 84.200.69.80\n" + + "auth-user-pass\n" + + "\n" + + "remote xx.xx.xx.xx 1194\n" + + "http-proxy 1.2.3.4 8080\n" + + "http-proxy-option VERSION 1.1\n" + + "http-proxy-option CUSTOM-HEADER \"Connection: Upgrade\"\n" + + "http-proxy-option CUSTOM-HEADER \"X-Forwarded-Proto: https\"\n" + + "http-proxy-option CUSTOM-HEADER \"Upgrade-Insecure-Requests: 1\"\n" + + "http-proxy-option CUSTOM-HEADER \"DNT: 1\"\n" + + "http-proxy-option CUSTOM-HEADER \"Tk: N\"\n" + + "\n" + + "\n" + + "-----BEGIN CERTIFICATE-----\n" + + "\n" + + "-----END CERTIFICATE-----\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "-----BEGIN CERTIFICATE-----\n" + + "\n" + + "-----END CERTIFICATE-----\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "-----BEGIN PRIVATE KEY-----\n" + + "\n" + + "-----END PRIVATE KEY-----\n" + + "\n" + + ""; + + ConfigParser cp = new ConfigParser(); + cp.parseConfig(new StringReader(proxyconf)); + VpnProfile vp = cp.convertProfile(); + String config = vp.getConfigFile(RuntimeEnvironment.application, true); + + + Assert.assertEquals(vp.checkProfile(RuntimeEnvironment.application, true), R.string.no_error_found); + Assert.assertEquals(vp.checkProfile(RuntimeEnvironment.application, false), R.string.no_error_found); + + config = vp.getConfigFile(RuntimeEnvironment.application, false); + + Assert.assertTrue(config.contains("http-proxy 1.2.3.4")); + Assert.assertFalse(config.contains("management-query-proxy")); + + + Assert.assertTrue(config.contains("http-proxy-option CUSTOM-HEADER")); + + vp.mConnections = Arrays.copyOf(vp.mConnections, vp.mConnections.length + 1); + vp.mConnections[vp.mConnections.length - 1] = new Connection(); + + vp.mConnections[vp.mConnections.length -1].mProxyType = Connection.ProxyType.ORBOT; + + Assert.assertEquals(vp.checkProfile(RuntimeEnvironment.application, false), R.string.error_orbot_and_proxy_options); + + } + } -- cgit v1.2.3