summaryrefslogtreecommitdiff
path: root/main/src
diff options
context:
space:
mode:
Diffstat (limited to 'main/src')
-rw-r--r--main/src/main/java/de/blinkt/openvpn/VpnProfile.java202
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java338
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/Connection.java10
-rwxr-xr-xmain/src/main/res/values/strings.xml1
-rw-r--r--main/src/test/java/de/blinkt/openvpn/core/TestConfigGenerator.java1
-rw-r--r--main/src/test/java/de/blinkt/openvpn/core/TestConfigParser.java74
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 += "<ca>\n" + ks[0] + "\n</ca>\n";
+ cfg.append("<ca>\n").append(ks[0]).append("\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";
+ cfg.append("<extra-certs>\n").append(ks[1]).append("\n</extra-certs>\n");
+ cfg.append("<cert>\n").append(ks[2]).append("\n</cert>\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 += "<connection>\n";
- cfg += conn.getConnectionBlock(configForOvpn3);
- cfg += "</connection>\n";
+ cfg.append("<connection>\n");
+ cfg.append(conn.getConnectionBlock(configForOvpn3));
+ cfg.append("</connection>\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<String> connectionOptionsSet = new HashSet<>(Arrays.asList(connectionOptions));
+
private HashMap<String, Vector<Vector<String>>> options = new HashMap<>();
private HashMap<String, Vector<String>> meta = new HashMap<String, Vector<String>>();
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<String, String> 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<String> parseline(String line) throws ConfigParseError {
Vector<String> parameters = new Vector<String>();
@@ -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<String> 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<Vector<String>> tlsauthoptions = getAllOption("tls-auth", 1, 2);
@@ -429,7 +427,7 @@ public class ConfigParser {
np.mTLSAuthDirection = direction.get(1);
Vector<String> 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<String> mode = getOption("mode", 1, 1);
if (mode != null) {
if (!mode.get(1).equals("p2p"))
@@ -595,9 +592,9 @@ public class ConfigParser {
}
- Vector<String> x509usernamefield = getOption("x509-username-field", 1,1);
- if (x509usernamefield!=null) {
- np.mx509UsernameField = x509usernamefield.get(1);
+ Vector<String> x509usernamefield = getOption("x509-username-field", 1, 1);
+ if (x509usernamefield != null) {
+ np.mx509UsernameField = x509usernamefield.get(1);
}
@@ -812,7 +809,7 @@ public class ConfigParser {
}
Vector<String> 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<Vector<String>> remotes = getAllOption("remote", 1, 3);
- // Assume that we need custom options if connectionDefault are set
- if (connDefault != null) {
- for (Vector<Vector<String>> option : options.values()) {
-
- conn.mCustomConfiguration += getOptionStrings(option);
+ Vector <String> optionsToRemove = new Vector<>();
+ // Assume that we need custom options if connectionDefault are set or in the connection specific set
+ for (Map.Entry<String, Vector<Vector<String>>> 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<Vector<String>>();
@@ -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<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;
- else if (redirect.get(i).equals("!ipv4"))
- noIpv4=true;
- else if (redirect.get(i).equals("ipv6"))
- np.mUseDefaultRoutev6=true;
- }
+ 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;
+ 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<String> 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<Vector<String>> getAllOption(String option, int minarg, int maxarg) throws ConfigParseError {
Vector<Vector<String>> 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, "<http-proxy-user-pass>\n%s\n%s\n</http-proxy-user-pass>\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 @@
<string name="faq_remote_api_title">Remote API</string>
<string name="faq_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. &lt;p>Examples using adb shell and the intents. Replace profilname with your profile name&lt;p>&lt;p> adb shell am start-activity -a android.intent.action.MAIN de.blinkt.openvpn/.api.DisconnectVPN&lt;p> adb shell am start-activity -a android.intent.action.MAIN -e de.blinkt.openvpn.api.profileName Blinkt de.blinkt.openvpn/.api.ConnectVPN</string>
<string name ="enableproxyauth">Enable Proxy Authentication</string>
+ <string name="error_orbot_and_proxy_options">Cannot use extra http-proxy-option statement and Orbot integration at the same timeO</string>
</resources>
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" +
+ "<ca>\n" +
+ "-----BEGIN CERTIFICATE-----\n" +
+ "\n" +
+ "-----END CERTIFICATE-----\n" +
+ "\n" +
+ "</ca>\n" +
+ "\n" +
+ "<cert>\n" +
+ "-----BEGIN CERTIFICATE-----\n" +
+ "\n" +
+ "-----END CERTIFICATE-----\n" +
+ "\n" +
+ "</cert>\n" +
+ "\n" +
+ "<key>\n" +
+ "-----BEGIN PRIVATE KEY-----\n" +
+ "\n" +
+ "-----END PRIVATE KEY-----\n" +
+ "\n" +
+ "</key>";
+
+ 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);
+
+ }
+
}