diff options
-rw-r--r-- | main/src/main/java/de/blinkt/openvpn/VpnProfile.java | 31 | ||||
-rw-r--r-- | main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java | 28 | ||||
-rw-r--r-- | main/src/main/res/values/arrays.xml | 6 | ||||
-rwxr-xr-x | main/src/main/res/values/strings.xml | 2 | ||||
-rw-r--r-- | main/src/test/java/de/blinkt/openvpn/core/TestConfigParser.kt | 46 | ||||
-rw-r--r-- | main/src/ui/java/de/blinkt/openvpn/activities/ConfigConverter.kt | 13 | ||||
-rw-r--r-- | main/src/ui/java/de/blinkt/openvpn/fragments/Settings_Basic.java | 6 | ||||
-rw-r--r-- | main/src/ui/java/de/blinkt/openvpn/fragments/Utils.java | 299 | ||||
-rw-r--r-- | main/src/ui/java/de/blinkt/openvpn/fragments/Utils.kt | 261 | ||||
-rw-r--r-- | main/src/ui/res/layout/basic_settings.xml | 11 | ||||
-rw-r--r-- | main/src/ui/res/layout/config_converter.xml | 14 |
11 files changed, 389 insertions, 328 deletions
diff --git a/main/src/main/java/de/blinkt/openvpn/VpnProfile.java b/main/src/main/java/de/blinkt/openvpn/VpnProfile.java index 5c2edd6b..9146af00 100644 --- a/main/src/main/java/de/blinkt/openvpn/VpnProfile.java +++ b/main/src/main/java/de/blinkt/openvpn/VpnProfile.java @@ -164,16 +164,17 @@ public class VpnProfile implements Serializable, Cloneable { public String mServerPort = "1194"; public boolean mUseUdp = true; public boolean mTemporaryProfile = false; + public String mDataCiphers = ""; + public boolean mBlockUnusedAddressFamilies = true; + public boolean mCheckPeerFingerprint = false; + public String mPeerFingerPrints = ""; + public int mCompatMode = 0; + private transient PrivateKey mPrivateKey; // Public attributes, since I got mad with getter/setter // set members to default values private UUID mUuid; private int mProfileVersion; - public String mDataCiphers = ""; - - public boolean mBlockUnusedAddressFamilies =true; - public boolean mCheckPeerFingerprint = false; - public String mPeerFingerPrints = ""; public VpnProfile(String name) { mUuid = UUID.randomUUID(); @@ -313,10 +314,9 @@ public class VpnProfile implements Serializable, Cloneable { c.mProxyType = Connection.ProxyType.NONE; case 7: if (mAllowAppVpnBypass) - mBlockUnusedAddressFamilies = !mAllowAppVpnBypass; + mBlockUnusedAddressFamilies = false; case 8: - if (!TextUtils.isEmpty(mCipher) && !mCipher.equals("AES-256-GCM") && !mCipher.equals("AES-128-GCM")) - { + if (!TextUtils.isEmpty(mCipher) && !mCipher.equals("AES-256-GCM") && !mCipher.equals("AES-128-GCM")) { mDataCiphers = "AES-256-GCM:AES-128-GCM:" + mCipher; } default: @@ -457,8 +457,7 @@ public class VpnProfile implements Serializable, Cloneable { case VpnProfile.TYPE_PKCS12: cfg.append(insertFileData("pkcs12", mPKCS12Filename)); - if (!TextUtils.isEmpty(mCaFilename)) - { + if (!TextUtils.isEmpty(mCaFilename)) { cfg.append(insertFileData("ca", mCaFilename)); } break; @@ -640,11 +639,19 @@ public class VpnProfile implements Serializable, Cloneable { cfg.append("remote-cert-tls server\n"); } - if (!TextUtils.isEmpty(mDataCiphers)) - { + if (!TextUtils.isEmpty(mDataCiphers)) { cfg.append("data-ciphers ").append(mDataCiphers).append("\n"); } + if (mCompatMode > 0) + { + int major = mCompatMode/10000; + int minor = mCompatMode % 10000/100; + int patch = mCompatMode % 100; + cfg.append(String.format(Locale.US, "compat-mode %d.%d.%d\n", major, minor, patch)); + + } + if (!TextUtils.isEmpty(mCipher)) { cfg.append("cipher ").append(mCipher).append("\n"); } 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 d25c20c0..a1b1bcb6 100644 --- a/main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java +++ b/main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java @@ -538,24 +538,26 @@ public class ConfigParser { if (cipher != null) np.mCipher = cipher.get(1); - if (data_ciphers == null) - { - data_ciphers = ncp_ciphers; - } - - /* The world is not yet ready to only use data-ciphers, add --cipher to data-ciphers - * for now on import */ if (data_ciphers != null) { np.mDataCiphers = data_ciphers.get(1); + } + else if (ncp_ciphers != null) + { + np.mDataCiphers = ncp_ciphers.get(1); + } - if (!TextUtils.isEmpty(np.mCipher) && !np.mDataCiphers.contains(np.mCipher)) - { - np.mDataCiphers += ":" + np.mCipher; - } - } else if (!TextUtils.isEmpty(np.mCipher) && !np.mCipher.equals("AES-128-GCM") && !np.mCipher.equals("AES-256")) + + Vector<String> compatmode = getOption("compat-mode", 1, 1); + if (compatmode != null) { - np.mDataCiphers += "AES-256-GCM:AES-128-GCM:" + np.mCipher; + Scanner versionScanner = new Scanner(compatmode.get(1)); + versionScanner.useDelimiter("\\."); + int major = versionScanner.nextInt(); + int minor = versionScanner.nextInt(); + int patch = versionScanner.nextInt(); + + np.mCompatMode = major * 10000 + minor * 100 + patch; } Vector<String> auth = getOption("auth", 1, 1); diff --git a/main/src/main/res/values/arrays.xml b/main/src/main/res/values/arrays.xml index 3b7c42e1..97c10ff7 100644 --- a/main/src/main/res/values/arrays.xml +++ b/main/src/main/res/values/arrays.xml @@ -36,4 +36,10 @@ <item>Disconnect, keep password</item> <item>Ignore, retry</item> </string-array> + <string-array name="compat_mode"> + <item>Modern defaults</item> + <item>OpenVPN 2.5.x peers</item> + <item>OpenVPN 2.4.x peers</item> + <item>OpenVPN 2.3.x and older peers</item> + </string-array> </resources> diff --git a/main/src/main/res/values/strings.xml b/main/src/main/res/values/strings.xml index 7cab17e4..2de43eb2 100755 --- a/main/src/main/res/values/strings.xml +++ b/main/src/main/res/values/strings.xml @@ -509,5 +509,7 @@ <string name="trigger_pending_auth_dialog">Press here open a window to enter additional required authentication</string> <string name="import_from_access_server">OpenVPN Access Server</string> <string name="import_from_URL">URL</string> + <string name="compatmode">Compatiblity Mode</string> + <string name="compat_mode_label">Compatibility mode</string> </resources> diff --git a/main/src/test/java/de/blinkt/openvpn/core/TestConfigParser.kt b/main/src/test/java/de/blinkt/openvpn/core/TestConfigParser.kt index fbaa4be2..c9dd64a2 100644 --- a/main/src/test/java/de/blinkt/openvpn/core/TestConfigParser.kt +++ b/main/src/test/java/de/blinkt/openvpn/core/TestConfigParser.kt @@ -9,6 +9,7 @@ import android.content.Context import android.os.Build import androidx.test.core.app.ApplicationProvider import de.blinkt.openvpn.R +import de.blinkt.openvpn.fragments.Utils import org.junit.Assert import org.junit.Test import org.junit.runner.RunWith @@ -145,7 +146,7 @@ class TestConfigParser { cp.parseConfig(StringReader(config4)) val vp4 = cp.convertProfile() - Assert.assertEquals("AES-128-GCM:AES-256-GCM:CHACHA20-POLY1305:BF-CBC", vp4.mDataCiphers) + Assert.assertEquals("AES-128-GCM:AES-256-GCM:CHACHA20-POLY1305", vp4.mDataCiphers) @@ -153,6 +154,49 @@ class TestConfigParser { @Test + fun testCompatmodeImport() { + val config = ("client\n" + + "tun-mtu 1234\n" + + "<connection>\n" + + "remote foo.bar\n" + + "tun-mtu 1222\n" + + "</connection>\n" + + "<cert>\nfakecert\n</cert>\n" + + "<key>\nfakekey\n</key>\n" + + "route 8.8.8.8 255.255.255.255 net_gateway\n") + val c:Context = ApplicationProvider.getApplicationContext() + + val config1 = config + "compat-mode 2.7.7\n" + + val cp = ConfigParser() + cp.parseConfig(StringReader(config1)) + val vp = cp.convertProfile() + + Assert.assertEquals(20707, vp.mCompatMode) + Assert.assertEquals(0,Utils.mapCompatVer(vp.mCompatMode)) + + + val config2 = config + "compat-mode 2.4.0\n" + + + cp.parseConfig(StringReader(config2)) + val vp2 = cp.convertProfile() + Assert.assertEquals(20400, vp2.mCompatMode) + Assert.assertEquals(2,Utils.mapCompatVer(vp2.mCompatMode)) + val conf2 = vp2.getConfigFile(c, false) + Assert.assertTrue(conf2.contains("compat-mode 2.4.0")); + + val config3 = config + "compat-mode 1.17.23\n"; + cp.parseConfig(StringReader(config3)) + val vp3 = cp.convertProfile() + Assert.assertEquals(11723, vp3.mCompatMode) + Assert.assertEquals(3,Utils.mapCompatVer(vp3.mCompatMode)) + + val conf = vp3.getConfigFile(c, false) + Assert.assertTrue(conf.contains("compat-mode 1.17.23")); + } + + @Test @Throws(IOException::class, ConfigParser.ConfigParseError::class) fun testSockProxyImport() { val proxy = "ca baz\n" + diff --git a/main/src/ui/java/de/blinkt/openvpn/activities/ConfigConverter.kt b/main/src/ui/java/de/blinkt/openvpn/activities/ConfigConverter.kt index d01797f2..6ccf4c8a 100644 --- a/main/src/ui/java/de/blinkt/openvpn/activities/ConfigConverter.kt +++ b/main/src/ui/java/de/blinkt/openvpn/activities/ConfigConverter.kt @@ -55,6 +55,8 @@ class ConfigConverter : BaseActivity(), FileSelectCallback, View.OnClickListener private val mLogEntries = Vector<String>() private var mSourceUri: Uri? = null private lateinit var mProfilename: EditText + private lateinit var mCompatmode: Spinner + private lateinit var mCompatmodeLabel: TextView private var mImportTask: AsyncTask<Void, Void, Int>? = null private lateinit var mLogLayout: LinearLayout private lateinit var mProfilenameLabel: TextView @@ -124,6 +126,8 @@ class ConfigConverter : BaseActivity(), FileSelectCallback, View.OnClickListener return true } + mResult!!.mCompatMode = Utils.mapCompatMode(mCompatmode.selectedItemPosition) + val `in` = installPKCS12() if (`in` != null) @@ -573,12 +577,17 @@ class ConfigConverter : BaseActivity(), FileSelectCallback, View.OnClickListener mProfilename = findViewById<View>(R.id.profilename) as EditText mProfilenameLabel = findViewById<View>(R.id.profilename_label) as TextView + mCompatmode = findViewById(R.id.compatmode) as Spinner + mCompatmodeLabel = findViewById(R.id.compatmode_label) as TextView + + if (savedInstanceState != null && savedInstanceState.containsKey(VPNPROFILE)) { mResult = savedInstanceState.getSerializable(VPNPROFILE) as VpnProfile? mAliasName = savedInstanceState.getString("mAliasName") mEmbeddedPwFile = savedInstanceState.getString("pwfile") mSourceUri = savedInstanceState.getParcelable("mSourceUri") mProfilename.setText(mResult!!.mName) + mCompatmode.setSelection(Utils.mapCompatVer(mResult!!.mCompatMode)) if (savedInstanceState.containsKey("logentries")) { @@ -714,6 +723,10 @@ class ConfigConverter : BaseActivity(), FileSelectCallback, View.OnClickListener mProfilenameLabel.visibility = View.VISIBLE mProfilename.setText(mResult!!.name) + mCompatmode.visibility = View.VISIBLE + mCompatmodeLabel.visibility = View.VISIBLE + mCompatmode.setSelection(Utils.mapCompatVer(mResult!!.mCompatMode)) + log(R.string.import_done) } } diff --git a/main/src/ui/java/de/blinkt/openvpn/fragments/Settings_Basic.java b/main/src/ui/java/de/blinkt/openvpn/fragments/Settings_Basic.java index 0afb754d..0899dd13 100644 --- a/main/src/ui/java/de/blinkt/openvpn/fragments/Settings_Basic.java +++ b/main/src/ui/java/de/blinkt/openvpn/fragments/Settings_Basic.java @@ -17,7 +17,6 @@ import android.widget.AdapterView.OnItemSelectedListener; import de.blinkt.openvpn.R; import de.blinkt.openvpn.R.id; import de.blinkt.openvpn.VpnProfile; -import de.blinkt.openvpn.core.ProfileManager; import de.blinkt.openvpn.views.FileSelectLayout; public class Settings_Basic extends KeyChainSettingsFragment implements OnItemSelectedListener, FileSelectLayout.FileSelectCallback, CompoundButton.OnCheckedChangeListener { @@ -28,6 +27,7 @@ public class Settings_Basic extends KeyChainSettingsFragment implements OnItemSe private FileSelectLayout mClientKey; private CheckBox mUseLzo; private Spinner mType; + private Spinner mCompatMode; private FileSelectLayout mpkcs12; private FileSelectLayout mCrlFile; private TextView mPKCS12Password; @@ -69,6 +69,7 @@ public class Settings_Basic extends KeyChainSettingsFragment implements OnItemSe mCrlFile = mView.findViewById(id.crlfile); mUseLzo = mView.findViewById(id.lzo); mType = mView.findViewById(id.type); + mCompatMode = mView.findViewById(id.compatmode); mPKCS12Password = mView.findViewById(id.pkcs12password); mEnablePeerFingerprint = mView.findViewById(id.enable_peer_fingerprint); mPeerFingerprints = mView.findViewById(id.peer_fingerprint); @@ -191,6 +192,7 @@ public class Settings_Basic extends KeyChainSettingsFragment implements OnItemSe mUseLzo.setChecked(mProfile.mUseLzo); mType.setSelection(mProfile.mAuthenticationType); + mCompatMode.setSelection(Utils.mapCompatVer(mProfile.mCompatMode)); mpkcs12.setData(mProfile.mPKCS12Filename, getActivity()); mPKCS12Password.setText(mProfile.mPKCS12Password); mUserName.setText(mProfile.mUsername); @@ -220,7 +222,7 @@ public class Settings_Basic extends KeyChainSettingsFragment implements OnItemSe mProfile.mAuthRetry = mAuthRetry.getSelectedItemPosition(); mProfile.mCheckPeerFingerprint = mEnablePeerFingerprint.isChecked(); mProfile.mPeerFingerPrints = mPeerFingerprints.getText().toString(); - + mProfile.mCompatMode = Utils.mapCompatMode(mCompatMode.getSelectedItemPosition()); } @Override diff --git a/main/src/ui/java/de/blinkt/openvpn/fragments/Utils.java b/main/src/ui/java/de/blinkt/openvpn/fragments/Utils.java deleted file mode 100644 index 3692624b..00000000 --- a/main/src/ui/java/de/blinkt/openvpn/fragments/Utils.java +++ /dev/null @@ -1,299 +0,0 @@ -/* - * Copyright (c) 2012-2016 Arne Schwabe - * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt - */ - -package de.blinkt.openvpn.fragments; - -import android.annotation.TargetApi; -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.database.Cursor; -import android.net.Uri; -import android.os.Build; -import android.provider.OpenableColumns; -import android.util.Base64; -import android.webkit.MimeTypeMap; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.List; -import java.util.TreeSet; -import java.util.Vector; - -import de.blinkt.openvpn.VpnProfile; -import de.blinkt.openvpn.core.Preferences; - -public class Utils { - - - @TargetApi(Build.VERSION_CODES.KITKAT) - public static Intent getFilePickerIntent(Context c, FileType fileType) { - Intent i = new Intent(Intent.ACTION_GET_CONTENT); - i.addCategory(Intent.CATEGORY_OPENABLE); - TreeSet<String> supportedMimeTypes = new TreeSet<String>(); - Vector<String> extensions = new Vector<>(); - - switch (fileType) { - case PKCS12: - i.setType("application/x-pkcs12"); - supportedMimeTypes.add("application/x-pkcs12"); - extensions.add("p12"); - extensions.add("pfx"); - break; - case CLIENT_CERTIFICATE: - case CA_CERTIFICATE: - i.setType("application/x-pem-file"); - supportedMimeTypes.add("application/x-x509-ca-cert"); - supportedMimeTypes.add("application/x-x509-user-cert"); - supportedMimeTypes.add("application/x-pem-file"); - supportedMimeTypes.add("application/pkix-cert"); - supportedMimeTypes.add("text/plain"); - - extensions.add("pem"); - extensions.add("crt"); - extensions.add("cer"); - break; - case KEYFILE: - i.setType("application/x-pem-file"); - supportedMimeTypes.add("application/x-pem-file"); - supportedMimeTypes.add("application/pkcs8"); - - // Google drive .... - supportedMimeTypes.add("application/x-iwork-keynote-sffkey"); - extensions.add("key"); - break; - - case TLS_AUTH_FILE: - i.setType("text/plain"); - - // Backup .... - supportedMimeTypes.add("application/pkcs8"); - // Google Drive is kind of crazy ..... - supportedMimeTypes.add("application/x-iwork-keynote-sffkey"); - - extensions.add("txt"); - extensions.add("key"); - break; - - case OVPN_CONFIG: - i.setType("application/x-openvpn-profile"); - supportedMimeTypes.add("application/x-openvpn-profile"); - supportedMimeTypes.add("application/openvpn-profile"); - supportedMimeTypes.add("application/ovpn"); - supportedMimeTypes.add("text/plain"); - extensions.add("ovpn"); - extensions.add("conf"); - break; - - case CRL_FILE: - supportedMimeTypes.add("application/x-pkcs7-crl"); - supportedMimeTypes.add("application/pkix-crl"); - extensions.add("crl"); - break; - - case USERPW_FILE: - i.setType("text/plain"); - supportedMimeTypes.add("text/plain"); - break; - } - - MimeTypeMap mtm = MimeTypeMap.getSingleton(); - - for (String ext : extensions) { - String mimeType = mtm.getMimeTypeFromExtension(ext); - if (mimeType != null) - supportedMimeTypes.add(mimeType); - } - - // Always add this as fallback - supportedMimeTypes.add("application/octet-stream"); - - i.putExtra(Intent.EXTRA_MIME_TYPES, supportedMimeTypes.toArray(new String[supportedMimeTypes.size()])); - - // People don't know that this is actually a system setting. Override it ... - // DocumentsContract.EXTRA_SHOW_ADVANCED is hidden - i.putExtra("android.content.extra.SHOW_ADVANCED", true); - - /* Samsung has decided to do something strange, on stock Android GET_CONTENT opens the document UI */ - /* fist try with documentsui */ - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) - i.setPackage("com.android.documentsui"); - - - /* - * Android 11 is much stricter about allowing what to query. Since the app has the - * QUERY_ALL permission we can still check on Android 11 but otherwise we would just - * assume the documents ui to be always there: - */ - - /* - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - return i; - }*/ - - - - //noinspection ConstantConditions - if (!isIntentAvailable(c,i)) { - i.setAction(Intent.ACTION_OPEN_DOCUMENT); - i.setPackage(null); - - // Check for really broken devices ... :( - if (!isIntentAvailable(c,i)) { - return null; - } - } - - - /* - final PackageManager packageManager = c.getPackageManager(); - ResolveInfo list = packageManager.resolveActivity(i, 0); - - Toast.makeText(c, "Starting package: "+ list.activityInfo.packageName - + "with ACTION " + i.getAction(), Toast.LENGTH_LONG).show(); - - */ - return i; - } - - public static boolean alwaysUseOldFileChooser(Context c) - { - /* Android P does not allow access to the file storage anymore */ - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) - return false; - - SharedPreferences prefs = Preferences.getDefaultSharedPreferences(c); - return prefs.getBoolean("useInternalFileSelector", false); - } - - public static boolean isIntentAvailable(Context context, Intent i) { - final PackageManager packageManager = context.getPackageManager(); - List<ResolveInfo> list = - packageManager.queryIntentActivities(i, - PackageManager.MATCH_DEFAULT_ONLY); - - // Ignore the Android TV framework app in the list - int size = list.size(); - for (ResolveInfo ri: list) - { - // Ignore stub apps - if ("com.google.android.tv.frameworkpackagestubs".equals(ri.activityInfo.packageName)) - { - size--; - } - } - - return size > 0; - } - - - public enum FileType { - PKCS12(0), - CLIENT_CERTIFICATE(1), - CA_CERTIFICATE(2), - OVPN_CONFIG(3), - KEYFILE(4), - TLS_AUTH_FILE(5), - USERPW_FILE(6), - CRL_FILE(7); - - private int value; - - FileType(int i) { - value = i; - } - - public static FileType getFileTypeByValue(int value) { - switch (value) { - case 0: - return PKCS12; - case 1: - return CLIENT_CERTIFICATE; - case 2: - return CA_CERTIFICATE; - case 3: - return OVPN_CONFIG; - case 4: - return KEYFILE; - case 5: - return TLS_AUTH_FILE; - case 6: - return USERPW_FILE; - case 7: - return CRL_FILE; - default: - return null; - } - } - - public int getValue() { - return value; - } - } - - static private byte[] readBytesFromStream(InputStream input) throws IOException { - - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - - int nRead; - byte[] data = new byte[16384]; - - ; - - long totalread = 0; - while ((nRead = input.read(data, 0, data.length)) != -1 && totalread <VpnProfile.MAX_EMBED_FILE_SIZE ) { - buffer.write(data, 0, nRead); - totalread+=nRead; - } - - buffer.flush(); - input.close(); - return buffer.toByteArray(); - } - - public static String getFilePickerResult(FileType ft, Intent result, Context c) throws IOException, SecurityException { - - Uri uri = result.getData(); - if (uri == null) - return null; - - byte[] fileData = readBytesFromStream(c.getContentResolver().openInputStream(uri)); - String newData = null; - - Cursor cursor = c.getContentResolver().query(uri, null, null, null, null); - - String prefix = ""; - try { - if (cursor!=null && cursor.moveToFirst()) { - int cidx = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); - if (cidx != -1) { - String displayName = cursor.getString(cidx); - - if (!displayName.contains(VpnProfile.INLINE_TAG) && !displayName.contains(VpnProfile.DISPLAYNAME_TAG)) - prefix = VpnProfile.DISPLAYNAME_TAG + displayName; - } - } - } finally { - if(cursor!=null) - cursor.close(); - } - - switch (ft) { - case PKCS12: - newData = Base64.encodeToString(fileData, Base64.DEFAULT); - break; - default: - newData = new String(fileData, "UTF-8"); - break; - } - - return prefix + VpnProfile.INLINE_TAG + newData; - } - -} diff --git a/main/src/ui/java/de/blinkt/openvpn/fragments/Utils.kt b/main/src/ui/java/de/blinkt/openvpn/fragments/Utils.kt new file mode 100644 index 00000000..64a8d3ba --- /dev/null +++ b/main/src/ui/java/de/blinkt/openvpn/fragments/Utils.kt @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2012-2016 Arne Schwabe + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt + */ +package de.blinkt.openvpn.fragments + +import android.annotation.TargetApi +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Build +import android.provider.OpenableColumns +import android.util.Base64 +import android.webkit.MimeTypeMap +import kotlin.Throws +import de.blinkt.openvpn.VpnProfile +import de.blinkt.openvpn.core.Preferences +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.io.InputStream +import java.nio.charset.Charset +import java.util.* + +object Utils { + @JvmStatic + @TargetApi(Build.VERSION_CODES.KITKAT) + fun getFilePickerIntent(c: Context, fileType: FileType?): Intent? { + val i = Intent(Intent.ACTION_GET_CONTENT) + i.addCategory(Intent.CATEGORY_OPENABLE) + val supportedMimeTypes = TreeSet<String>() + val extensions = Vector<String>() + when (fileType) { + FileType.PKCS12 -> { + i.type = "application/x-pkcs12" + supportedMimeTypes.add("application/x-pkcs12") + extensions.add("p12") + extensions.add("pfx") + } + FileType.CLIENT_CERTIFICATE, FileType.CA_CERTIFICATE -> { + i.type = "application/x-pem-file" + supportedMimeTypes.add("application/x-x509-ca-cert") + supportedMimeTypes.add("application/x-x509-user-cert") + supportedMimeTypes.add("application/x-pem-file") + supportedMimeTypes.add("application/pkix-cert") + supportedMimeTypes.add("text/plain") + extensions.add("pem") + extensions.add("crt") + extensions.add("cer") + } + FileType.KEYFILE -> { + i.type = "application/x-pem-file" + supportedMimeTypes.add("application/x-pem-file") + supportedMimeTypes.add("application/pkcs8") + + // Google drive .... + supportedMimeTypes.add("application/x-iwork-keynote-sffkey") + extensions.add("key") + } + FileType.TLS_AUTH_FILE -> { + i.type = "text/plain" + + // Backup .... + supportedMimeTypes.add("application/pkcs8") + // Google Drive is kind of crazy ..... + supportedMimeTypes.add("application/x-iwork-keynote-sffkey") + extensions.add("txt") + extensions.add("key") + } + FileType.OVPN_CONFIG -> { + i.type = "application/x-openvpn-profile" + supportedMimeTypes.add("application/x-openvpn-profile") + supportedMimeTypes.add("application/openvpn-profile") + supportedMimeTypes.add("application/ovpn") + supportedMimeTypes.add("text/plain") + extensions.add("ovpn") + extensions.add("conf") + } + FileType.CRL_FILE -> { + supportedMimeTypes.add("application/x-pkcs7-crl") + supportedMimeTypes.add("application/pkix-crl") + extensions.add("crl") + } + FileType.USERPW_FILE -> { + i.type = "text/plain" + supportedMimeTypes.add("text/plain") + } + } + val mtm = MimeTypeMap.getSingleton() + for (ext in extensions) { + val mimeType = mtm.getMimeTypeFromExtension(ext) + if (mimeType != null) supportedMimeTypes.add(mimeType) + } + + // Always add this as fallback + supportedMimeTypes.add("application/octet-stream") + i.putExtra(Intent.EXTRA_MIME_TYPES, supportedMimeTypes.toTypedArray()) + + // People don't know that this is actually a system setting. Override it ... + // DocumentsContract.EXTRA_SHOW_ADVANCED is hidden + i.putExtra("android.content.extra.SHOW_ADVANCED", true) + + /* Samsung has decided to do something strange, on stock Android GET_CONTENT opens the document UI */ + /* fist try with documentsui */if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) i.setPackage( + "com.android.documentsui" + ) + + + /* + * Android 11 is much stricter about allowing what to query. Since the app has the + * QUERY_ALL permission we can still check on Android 11 but otherwise we would just + * assume the documents ui to be always there: + */ + + /* + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + return i; + }*/if (!isIntentAvailable(c, i)) { + i.action = Intent.ACTION_OPEN_DOCUMENT + i.setPackage(null) + + // Check for really broken devices ... :( + if (!isIntentAvailable(c, i)) { + return null + } + } + + + /* + final PackageManager packageManager = c.getPackageManager(); + ResolveInfo list = packageManager.resolveActivity(i, 0); + + Toast.makeText(c, "Starting package: "+ list.activityInfo.packageName + + "with ACTION " + i.getAction(), Toast.LENGTH_LONG).show(); + + */return i + } + + @JvmStatic + fun alwaysUseOldFileChooser(c: Context?): Boolean { + /* Android P does not allow access to the file storage anymore */ + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) return false + val prefs = Preferences.getDefaultSharedPreferences(c) + return prefs.getBoolean("useInternalFileSelector", false) + } + + fun isIntentAvailable(context: Context, i: Intent?): Boolean { + val packageManager = context.packageManager + val list = packageManager.queryIntentActivities( + i!!, + PackageManager.MATCH_DEFAULT_ONLY + ) + + // Ignore the Android TV framework app in the list + var size = list.size + for (ri in list) { + // Ignore stub apps + if ("com.google.android.tv.frameworkpackagestubs" == ri.activityInfo.packageName) { + size-- + } + } + return size > 0 + } + + @Throws(IOException::class) + private fun readBytesFromStream(input: InputStream?): ByteArray { + val buffer = ByteArrayOutputStream() + var nRead: Int + val data = ByteArray(16384) + var totalread: Long = 0 + while (input!!.read(data, 0, data.size) + .also { nRead = it } != -1 && totalread < VpnProfile.MAX_EMBED_FILE_SIZE + ) { + buffer.write(data, 0, nRead) + totalread += nRead.toLong() + } + buffer.flush() + input.close() + return buffer.toByteArray() + } + + @JvmStatic + @Throws(IOException::class, SecurityException::class) + fun getFilePickerResult(ft: FileType?, result: Intent, c: Context): String? { + val uri = result.data ?: return null + val fileData = readBytesFromStream(c.contentResolver.openInputStream(uri)) + var newData: String? = null + val cursor = c.contentResolver.query(uri, null, null, null, null) + var prefix = "" + try { + if (cursor != null && cursor.moveToFirst()) { + val cidx = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) + if (cidx != -1) { + val displayName = cursor.getString(cidx) + if (!displayName.contains(VpnProfile.INLINE_TAG) && !displayName.contains( + VpnProfile.DISPLAYNAME_TAG + ) + ) prefix = VpnProfile.DISPLAYNAME_TAG + displayName + } + } + } finally { + cursor?.close() + } + newData = when (ft) { + FileType.PKCS12 -> Base64.encodeToString( + fileData, + Base64.DEFAULT + ) + else -> String(fileData, Charset.forName("UTF-8")) + } + return prefix + VpnProfile.INLINE_TAG + newData + } + + enum class FileType(val value: Int) { + PKCS12(0), CLIENT_CERTIFICATE(1), CA_CERTIFICATE(2), OVPN_CONFIG(3), KEYFILE(4), TLS_AUTH_FILE( + 5 + ), + USERPW_FILE(6), CRL_FILE(7); + + companion object { + fun getFileTypeByValue(value: Int): FileType? { + return when (value) { + 0 -> PKCS12 + 1 -> CLIENT_CERTIFICATE + 2 -> CA_CERTIFICATE + 3 -> OVPN_CONFIG + 4 -> KEYFILE + 5 -> TLS_AUTH_FILE + 6 -> USERPW_FILE + 7 -> CRL_FILE + else -> null + } + } + } + } + + /* These functions make assumptions about the R.arrays.compat_mode contents */ + @JvmStatic + fun mapCompatVer(ver: Int): Int { + if (ver == 0 || ver >= 20600) + return 0 + else if (ver < 20400) + return 3 + else if (ver < 20500) + return 2 + else if (ver < 20600) + return 1 + return 0 + } + + @JvmStatic + + fun mapCompatMode(mode: Int): Int { + when (mode) { + 0 -> return 0 + 1 -> return 20500 + 2 -> return 20400 + 3 -> return 20300 + else -> return 0 + } + } +}
\ No newline at end of file diff --git a/main/src/ui/res/layout/basic_settings.xml b/main/src/ui/res/layout/basic_settings.xml index 6763e52f..dd2ed25e 100644 --- a/main/src/ui/res/layout/basic_settings.xml +++ b/main/src/ui/res/layout/basic_settings.xml @@ -28,7 +28,16 @@ style="@style/item" android:inputType="text" /> - + <TextView + style="@style/item" + android:text="@string/compat_mode_label" + android:textAppearance="?android:attr/textAppearanceSmall" /> + <Spinner + android:id="@+id/compatmode" + style="@style/item" + android:prompt="@string/compatmode" + android:entries="@array/compat_mode" + /> <CheckBox android:id="@+id/lzo" diff --git a/main/src/ui/res/layout/config_converter.xml b/main/src/ui/res/layout/config_converter.xml index 4070ff7c..b980dc59 100644 --- a/main/src/ui/res/layout/config_converter.xml +++ b/main/src/ui/res/layout/config_converter.xml @@ -36,6 +36,20 @@ android:inputType="text" /> <TextView + style="@style/item" + android:id="@+id/compatmode_label" + android:text="@string/compat_mode_label" + android:visibility="gone" + android:textAppearance="?android:attr/textAppearanceSmall" /> + <Spinner + android:id="@+id/compatmode" + style="@style/item" + android:visibility="gone" + android:prompt="@string/compatmode" + android:entries="@array/compat_mode" + /> + + <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/files_missing_hint" |