diff --git a/ChangeLog.md b/ChangeLog.md index 6ac034171..8593b551b 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1184,3 +1184,36 @@ **InviZible Pro beta 2.3.2** * Updated Tor. * Fixes and optimizations. + +**InviZible Pro beta 2.3.3** +* Updated Purple I2P to version 2.54.0. +* Updated Tor Snowflake bridge. +* Added option for downloading and updating DNSCrypt blacklists. +* Tor apps isolation is enabled by default. +* Fixed requesting Tor bridges via the app. +* Updated German and Spanish translations. + +**InviZible Pro beta 2.3.4** +* Implemented the ability to use the firewall when other modules are stopped. +* Added a stop button to the notification. +* Fixes and optimizations. + +**InviZible Pro beta 2.3.5** +* Updated Tor. +* Updated Tor Snowflake bridge. +* Updated Tor Lyrebird bridge to version 0.4.0. +* Implemented the ability to insert a list of DNSCrypt rules. +* Fixes and optimizations. + +**InviZible Pro beta 2.3.6** +* Added an option for quick access to Always-on VPN settings. +* Improvements and fixes for standalone firewall operation. +* Improvements for the firewall in root mode for Android versions above 5. +* Removed the Fastly rendezvous of the Tor snowflake bridge. +* Minor fixes and optimisations. + +**InviZible Pro beta 2.3.7** +* Updated Tor to version 4.8.13. +* Fixed detection of whether a cellular network interface can provide an internet connection. +* Fixed I2PD subscriptions option. +* Updated German, Spanish, Greek, Chinese, Portuguese (Brazil), Persian and Bulgarian translations. diff --git a/build.gradle b/build.gradle index b4e694686..0ac11c14d 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,9 @@ buildscript { ext { kotlin_version = '1.9.23' - dagger_version = '2.51.1' + dagger_version = '2.52' multidex_version = "2.0.1" - work_version = "2.9.0" + work_version = "2.9.1" } @@ -14,7 +14,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.5.1' + classpath 'com.android.tools.build:gradle:8.5.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" diff --git a/fastlane/metadata/android/en-US/changelogs/100233.txt b/fastlane/metadata/android/en-US/changelogs/100233.txt new file mode 100644 index 000000000..a0003d010 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/100233.txt @@ -0,0 +1,7 @@ +**InviZible Pro beta 2.3.3** +* Updated Purple I2P to version 2.54.0. +* Updated Tor Snowflake bridge. +* Added option for downloading and updating DNSCrypt blacklists. +* Tor apps isolation is enabled by default. +* Fixed requesting Tor bridges via the app. +* Updated German and Spanish translations. diff --git a/fastlane/metadata/android/en-US/changelogs/100234.txt b/fastlane/metadata/android/en-US/changelogs/100234.txt new file mode 100644 index 000000000..f2ba0dde5 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/100234.txt @@ -0,0 +1,4 @@ +**InviZible Pro beta 2.3.4** +* Implemented the ability to use the firewall when other modules are stopped. +* Added a stop button to the notification. +* Fixes and optimizations. diff --git a/fastlane/metadata/android/en-US/changelogs/100235.txt b/fastlane/metadata/android/en-US/changelogs/100235.txt new file mode 100644 index 000000000..6deef4e5b --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/100235.txt @@ -0,0 +1,6 @@ +**InviZible Pro beta 2.3.5** +* Updated Tor. +* Updated Tor Snowflake bridge. +* Updated Tor Lyrebird bridge to version 0.4.0. +* Implemented the ability to insert a list of DNSCrypt rules. +* Fixes and optimizations. diff --git a/fastlane/metadata/android/en-US/changelogs/100236.txt b/fastlane/metadata/android/en-US/changelogs/100236.txt new file mode 100644 index 000000000..2c28fbed5 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/100236.txt @@ -0,0 +1,6 @@ +**InviZible Pro beta 2.3.6** +* Added an option for quick access to Always-on VPN settings. +* Improvements and fixes for standalone firewall operation. +* Improvements for the firewall in root mode for Android versions above 5. +* Removed the Fastly rendezvous of the Tor snowflake bridge. +* Minor fixes and optimisations. diff --git a/fastlane/metadata/android/en-US/changelogs/100237.txt b/fastlane/metadata/android/en-US/changelogs/100237.txt new file mode 100644 index 000000000..8d302425b --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/100237.txt @@ -0,0 +1,5 @@ +**InviZible Pro beta 2.3.7** +* Updated Tor to version 4.8.13. +* Fixed detection of whether a cellular network interface can provide an internet connection. +* Fixed I2PD subscriptions option. +* Updated German, Spanish, Greek, Chinese, Portuguese (Brazil), Persian and Bulgarian translations. diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 85310727a..c87079a3a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -4,4 +4,4 @@ distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip -distributionSha256Sum=194717442575a6f96e1c1befa2c30e9a4fc90f701d7aee33eb879b79e7ff05c0 +distributionSha256Sum=distributionSha256Sum=194717442575a6f96e1c1befa2c30e9a4fc90f701d7aee33eb879b79e7ff05c0 diff --git a/tordnscrypt/build.gradle b/tordnscrypt/build.gradle index 8e0eee26a..7f49cb527 100644 --- a/tordnscrypt/build.gradle +++ b/tordnscrypt/build.gradle @@ -2,6 +2,7 @@ plugins { id 'com.android.application' id 'kotlin-android' id 'kotlin-kapt' + id "kotlin-parcelize" } android { @@ -15,7 +16,7 @@ android { fdroid{ applicationId "pan.alexander.tordnscrypt.stable" - versionName "6.8.0" + versionName "6.9.0" dimension = 'version' resValue 'string', 'package_name', applicationId } @@ -55,7 +56,7 @@ android { defaultConfig { minSdkVersion 19 targetSdkVersion 34 - versionCode 232 + versionCode 237 resConfigs "en", "ru", "pl", "de", "fa", "fi", "in", "fr", "ja", "zh", "es", "pt", "pt-rBR", "el", "tr", "it", "uk", "bg", "ar" @@ -125,7 +126,7 @@ android { } } - ndkVersion '23.0.7421159' + ndkVersion '23.1.7779620' buildFeatures { viewBinding true @@ -156,16 +157,16 @@ dependencies { implementation 'com.google.android.material:material:1.12.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.recyclerview:recyclerview:1.3.2' - implementation 'androidx.lifecycle:lifecycle-process:2.8.4' - implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.4' - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.4' + implementation 'androidx.lifecycle:lifecycle-process:2.8.6' + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.6' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.6' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.2.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' implementation 'eu.chainfire:libsuperuser:1.1.1' implementation 'com.jrummyapps:android-shell:1.0.1' implementation 'androidx.core:core-ktx:1.13.1' - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation project(':filepicker') diff --git a/tordnscrypt/libs/arm64-v8a/libi2pd.so b/tordnscrypt/libs/arm64-v8a/libi2pd.so index 62a6755a9..1c916b638 100644 Binary files a/tordnscrypt/libs/arm64-v8a/libi2pd.so and b/tordnscrypt/libs/arm64-v8a/libi2pd.so differ diff --git a/tordnscrypt/libs/arm64-v8a/libobfs4proxy.so b/tordnscrypt/libs/arm64-v8a/libobfs4proxy.so index 4719d8643..fa8fb54d5 100755 Binary files a/tordnscrypt/libs/arm64-v8a/libobfs4proxy.so and b/tordnscrypt/libs/arm64-v8a/libobfs4proxy.so differ diff --git a/tordnscrypt/libs/arm64-v8a/libsnowflake.so b/tordnscrypt/libs/arm64-v8a/libsnowflake.so index 40d0a2b37..e53399001 100755 Binary files a/tordnscrypt/libs/arm64-v8a/libsnowflake.so and b/tordnscrypt/libs/arm64-v8a/libsnowflake.so differ diff --git a/tordnscrypt/libs/arm64-v8a/libtor.so b/tordnscrypt/libs/arm64-v8a/libtor.so index 9a14c87f2..893ec4c37 100644 Binary files a/tordnscrypt/libs/arm64-v8a/libtor.so and b/tordnscrypt/libs/arm64-v8a/libtor.so differ diff --git a/tordnscrypt/libs/armeabi-v7a/libi2pd.so b/tordnscrypt/libs/armeabi-v7a/libi2pd.so index 535686e6d..73ba155ba 100644 Binary files a/tordnscrypt/libs/armeabi-v7a/libi2pd.so and b/tordnscrypt/libs/armeabi-v7a/libi2pd.so differ diff --git a/tordnscrypt/libs/armeabi-v7a/libobfs4proxy.so b/tordnscrypt/libs/armeabi-v7a/libobfs4proxy.so index b23067a11..e9cca1e04 100755 Binary files a/tordnscrypt/libs/armeabi-v7a/libobfs4proxy.so and b/tordnscrypt/libs/armeabi-v7a/libobfs4proxy.so differ diff --git a/tordnscrypt/libs/armeabi-v7a/libsnowflake.so b/tordnscrypt/libs/armeabi-v7a/libsnowflake.so index ca94dabcb..65954c469 100755 Binary files a/tordnscrypt/libs/armeabi-v7a/libsnowflake.so and b/tordnscrypt/libs/armeabi-v7a/libsnowflake.so differ diff --git a/tordnscrypt/libs/armeabi-v7a/libtor.so b/tordnscrypt/libs/armeabi-v7a/libtor.so index d68b0fd95..f820fbc7e 100644 Binary files a/tordnscrypt/libs/armeabi-v7a/libtor.so and b/tordnscrypt/libs/armeabi-v7a/libtor.so differ diff --git a/tordnscrypt/libs/build b/tordnscrypt/libs/build index 0aaa86368..c4476472e 100755 --- a/tordnscrypt/libs/build +++ b/tordnscrypt/libs/build @@ -6,7 +6,7 @@ cd "$( dirname "${BASH_SOURCE[0]}" )" ## Config ## ############ -ndk_version=21.3.6528147 +ndk_version=23.1.7779620 if [[ -d "/opt/android-sdk/ndk/${ndk_version}" ]] then NDK="/opt/android-sdk/ndk/${ndk_version}" @@ -78,7 +78,7 @@ popd ################ pushd libzmq/builds/android/ -export NDK_VERSION="android-ndk-r21c" +export NDK_VERSION="android-ndk-r23b" export ANDROID_NDK_ROOT=$NDK if [[ $ABI == arm64-v8a ]] then @@ -145,7 +145,7 @@ then NDK_PLATFORM_LEVEL=21 NDK_BIT=64 make showsetup else #compile armeabi-v7a things... - export APP_ABI=armeabi + export APP_ABI=armeabi-v7a make clean make make showsetup diff --git a/tordnscrypt/libs/prebuild b/tordnscrypt/libs/prebuild index 5b3c57a09..1936edf89 100755 --- a/tordnscrypt/libs/prebuild +++ b/tordnscrypt/libs/prebuild @@ -15,7 +15,7 @@ xz_version=v5.2.4 tor_version=prod i2pd_openssl_version=openssl-3.3.1 miniupnpc_version=miniupnpc_2_2_8 -i2pd_version=2.53.1 +i2pd_version=2.54.0 git clone --single-branch --branch $lyrebird_version https://gitlab.torproject.org/Gedsh/lyrebird diff --git a/tordnscrypt/owner.gradle b/tordnscrypt/owner.gradle index bde07b063..db8f28d36 100644 --- a/tordnscrypt/owner.gradle +++ b/tordnscrypt/owner.gradle @@ -2,6 +2,7 @@ plugins { id 'com.android.application' id 'kotlin-android' id 'kotlin-kapt' + id "kotlin-parcelize" } def keystorePropertiesFile = file("/home/alexander/.local/KStore/keystore.properties") @@ -34,7 +35,7 @@ android { productFlavors { lite { applicationId "pan.alexander.tordnscrypt.stable" - versionName "6.8.0" + versionName "6.9.0" dimension = 'version' signingConfig signingConfigs.stablesign resValue 'string', 'package_name', applicationId @@ -42,7 +43,7 @@ android { pro { applicationId "pan.alexander.tordnscrypt.stable" - versionName "6.8.0" + versionName "6.9.0" dimension = 'version' signingConfig signingConfigs.stablesign resValue 'string', 'package_name', applicationId @@ -50,7 +51,7 @@ android { beta { applicationId "pan.alexander.tordnscrypt" - versionName "2.3.2" + versionName "2.3.7" dimension = 'version' signingConfig signingConfigs.betasign resValue 'string', 'package_name', applicationId @@ -59,7 +60,7 @@ android { google_play { minSdkVersion 22 applicationId "pan.alexander.tordnscrypt.gp" - versionName "6.8.0" + versionName "6.9.0" dimension = 'version' signingConfig signingConfigs.stablesign resValue 'string', 'package_name', applicationId @@ -91,7 +92,7 @@ android { defaultConfig { minSdkVersion 19 targetSdkVersion 34 - versionCode 232 + versionCode 237 resConfigs "en", "ru", "pl", "de", "fa", "fi", "in", "fr", "ja", "zh", "es", "pt", "pt-rBR", "el", "tr", "it", "uk", "bg", "ar" @@ -167,7 +168,7 @@ android { } } - ndkVersion '23.0.7421159' + ndkVersion '23.1.7779620' buildFeatures { viewBinding true @@ -200,9 +201,9 @@ dependencies { implementation 'com.google.android.material:material:1.12.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.recyclerview:recyclerview:1.3.2' - implementation 'androidx.lifecycle:lifecycle-process:2.8.4' - implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.4' - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.4' + implementation 'androidx.lifecycle:lifecycle-process:2.8.6' + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.6' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.6' google_playImplementation 'com.android.billingclient:billing:6.2.0' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.2.1' @@ -210,7 +211,7 @@ dependencies { implementation 'eu.chainfire:libsuperuser:1.1.1' implementation 'com.jrummyapps:android-shell:1.0.1' implementation 'androidx.core:core-ktx:1.13.1' - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0' implementation project(':filepicker') //DI diff --git a/tordnscrypt/src/fdroid/AndroidManifest.xml b/tordnscrypt/src/fdroid/AndroidManifest.xml index f4b886b34..315350a9a 100644 --- a/tordnscrypt/src/fdroid/AndroidManifest.xml +++ b/tordnscrypt/src/fdroid/AndroidManifest.xml @@ -223,6 +223,10 @@ android:name=".settings.firewall.FirewallNotification" android:exported="false" /> + + diff --git a/tordnscrypt/src/google_play/AndroidManifest.xml b/tordnscrypt/src/google_play/AndroidManifest.xml index c5378a506..1140f899e 100644 --- a/tordnscrypt/src/google_play/AndroidManifest.xml +++ b/tordnscrypt/src/google_play/AndroidManifest.xml @@ -219,6 +219,10 @@ android:name=".settings.firewall.FirewallNotification" android:exported="false" /> + + diff --git a/tordnscrypt/src/main/AndroidManifest.xml b/tordnscrypt/src/main/AndroidManifest.xml index 3cd04ef2b..9d78d1120 100644 --- a/tordnscrypt/src/main/AndroidManifest.xml +++ b/tordnscrypt/src/main/AndroidManifest.xml @@ -206,6 +206,10 @@ android:name=".settings.firewall.FirewallNotification" android:exported="false" /> + + diff --git a/tordnscrypt/src/main/assets/dnscrypt.mp3 b/tordnscrypt/src/main/assets/dnscrypt.mp3 index 682339da2..97c82bbaa 100644 Binary files a/tordnscrypt/src/main/assets/dnscrypt.mp3 and b/tordnscrypt/src/main/assets/dnscrypt.mp3 differ diff --git a/tordnscrypt/src/main/assets/tor.mp3 b/tordnscrypt/src/main/assets/tor.mp3 index 1f5267fb2..ee19830ea 100644 Binary files a/tordnscrypt/src/main/assets/tor.mp3 and b/tordnscrypt/src/main/assets/tor.mp3 differ diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/MainActivity.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/MainActivity.java index eccf1c5b4..d84e0594c 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/MainActivity.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/MainActivity.java @@ -87,6 +87,7 @@ import pan.alexander.tordnscrypt.modules.ModulesRestarter; import pan.alexander.tordnscrypt.modules.ModulesService; import pan.alexander.tordnscrypt.modules.ModulesStatus; +import pan.alexander.tordnscrypt.modules.ModulesStatusBroadcaster; import pan.alexander.tordnscrypt.settings.PathVars; import pan.alexander.tordnscrypt.itpd_fragment.ITPDRunFragment; import pan.alexander.tordnscrypt.settings.SettingsActivity; @@ -105,9 +106,17 @@ import static pan.alexander.tordnscrypt.assistance.AccelerateDevelop.accelerated; import static pan.alexander.tordnscrypt.main_fragment.ViewPagerAdapter.MAIN_SCREEN_FRAGMENT_QUANTITY; +import static pan.alexander.tordnscrypt.modules.ModulesStatusBroadcaster.FIREWALL; +import static pan.alexander.tordnscrypt.modules.ModulesStatusBroadcaster.MODULE_ARG; +import static pan.alexander.tordnscrypt.modules.ModulesStatusBroadcaster.STATUS_ARG; +import static pan.alexander.tordnscrypt.modules.ModulesStatusBroadcaster.STATUS_RUNNING; +import static pan.alexander.tordnscrypt.modules.ModulesStatusBroadcaster.STATUS_STOPPED; import static pan.alexander.tordnscrypt.utils.Utils.isInterfaceLocked; +import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPING; import static pan.alexander.tordnscrypt.utils.logger.Logger.loge; import static pan.alexander.tordnscrypt.utils.logger.Logger.logi; +import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.FIREWALL_ENABLED; +import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.FIREWALL_WAS_STARTED; import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.FIX_TTL; import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.MAIN_ACTIVITY_RECREATE; import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.OPERATION_MODE; @@ -400,6 +409,8 @@ public boolean onPrepareOptionsMenu(Menu menu) { showNewTorIdentityIcon(menu); + manageFirewallIcon(menu); + return super.onPrepareOptionsMenu(menu); } @@ -463,7 +474,7 @@ private void switchIconsDependingOnMode(Menu menu, boolean rootIsAvailable) { } else if (mode == PROXY_MODE) { rootIcon.setIcon(R.drawable.ic_warning_white_24dp); } else { - rootIcon.setIcon(R.drawable.ic_vpn_key_white_24dp); + rootIcon.setIcon(R.drawable.ic_vpn_key); } if (mode == ROOT_MODE) { @@ -500,7 +511,7 @@ private void switchIconsDependingOnMode(Menu menu, boolean rootIsAvailable) { } else if (mode == PROXY_MODE) { rootIcon.setIcon(R.drawable.ic_warning_white_24dp); } else { - rootIcon.setIcon(R.drawable.ic_vpn_key_white_24dp); + rootIcon.setIcon(R.drawable.ic_vpn_key); } hotSpot.setVisible(false); @@ -587,6 +598,25 @@ public void showNewTorIdentityIcon(boolean show) { invalidateMenu(); } + private void manageFirewallIcon(Menu menu) { + MenuItem firewallMenuItem = menu.findItem(R.id.menu_item_firewall); + if (firewallMenuItem == null) { + return; + } + boolean firewallIsEnabled = preferenceRepository.get().getBoolPreference(FIREWALL_ENABLED) + && preferenceRepository.get().getBoolPreference(FIREWALL_WAS_STARTED); + if (modulesStatus.getMode() == PROXY_MODE) { + firewallMenuItem.setVisible(false); + } else { + firewallMenuItem.setVisible(firewallIsEnabled); + if (modulesStatus.getFirewallState() == RUNNING) { + firewallMenuItem.setIcon(R.drawable.ic_firewall_active_menu); + } else { + firewallMenuItem.setIcon(R.drawable.ic_firewall_menu); + } + } + } + @Override public boolean onOptionsItemSelected(@NonNull MenuItem item) { int id = item.getItemId(); @@ -614,6 +644,8 @@ public boolean onOptionsItemSelected(@NonNull MenuItem item) { switchToVPNMode(item); } else if (id == R.id.menu_proxies_mode) { switchToProxyMode(item); + } else if (id == R.id.menu_item_firewall) { + showFirewallScreen(); } return super.onOptionsItemSelected(item); } @@ -868,9 +900,7 @@ public boolean onNavigationItemSelected(@NonNull MenuItem item) { intent.setAction("common_Pref"); startActivity(intent); } else if (id == R.id.nav_firewall) { - Intent intent = new Intent(this, SettingsActivity.class); - intent.setAction("firewall"); - startActivity(intent); + showFirewallScreen(); } else if (id == R.id.nav_help) { Intent intent = new Intent(this, HelpActivity.class); startActivity(intent); @@ -904,6 +934,12 @@ public boolean onNavigationItemSelected(@NonNull MenuItem item) { return true; } + private void showFirewallScreen() { + Intent intent = new Intent(this, SettingsActivity.class); + intent.setAction("firewall"); + startActivity(intent); + } + private void showInfoAboutRoot() { boolean rootIsAvailable = preferenceRepository.get().getBoolPreference(ROOT_IS_AVAILABLE); boolean busyBoxIsAvailable = preferenceRepository.get().getBoolPreference("bbOK"); @@ -1008,14 +1044,25 @@ private void registerBroadcastReceiver() { mainActivityReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { + + if (intent == null) return; + if (ArpScannerKt.MITM_ATTACK_WARNING.equals(intent.getAction())) { invalidateMenu(); + } else if (ModulesStatusBroadcaster.STATUS_ACTION.equals(intent.getAction()) + && FIREWALL.equals(intent.getStringExtra(MODULE_ARG))) { + invalidateMenu(); + if (mainFragment != null) { + mainFragment.refreshStartButtonText(); + } } } }; IntentFilter mitmDetected = new IntentFilter(ArpScannerKt.MITM_ATTACK_WARNING); LocalBroadcastManager.getInstance(this).registerReceiver(mainActivityReceiver, mitmDetected); + IntentFilter firewallState = new IntentFilter(ModulesStatusBroadcaster.STATUS_ACTION); + LocalBroadcastManager.getInstance(this).registerReceiver(mainActivityReceiver, firewallState); } private void unregisterBroadcastReceiver() { @@ -1047,7 +1094,8 @@ protected void onStop() { if (ModulesService.serviceIsRunning && modulesStatus.getMode() == VPN_MODE && (modulesStatus.getDnsCryptState() == STOPPED || modulesStatus.getDnsCryptState() == FAULT || modulesStatus.getDnsCryptState() == ModuleState.UNDEFINED) && (modulesStatus.getTorState() == STOPPED || modulesStatus.getTorState() == FAULT || modulesStatus.getTorState() == ModuleState.UNDEFINED) - && (modulesStatus.getItpdState() == STOPPED || modulesStatus.getItpdState() == FAULT || modulesStatus.getItpdState() == ModuleState.UNDEFINED)) { + && (modulesStatus.getItpdState() == STOPPED || modulesStatus.getItpdState() == FAULT || modulesStatus.getItpdState() == ModuleState.UNDEFINED) + && (modulesStatus.getFirewallState() == STOPPING || modulesStatus.getFirewallState() == STOPPED)) { ModulesAux.stopModulesService(this); } @@ -1120,8 +1168,9 @@ public boolean onKeyLongPress(int keyCode, KeyEvent event) { @Override public void invalidateMenu() { - if (handler != null) { - handler.post(this::invalidateOptionsMenu); + Toolbar toolbar = getWindow().findViewById(R.id.toolbar); + if (toolbar != null) { + toolbar.post(this::invalidateOptionsMenu); } } } diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/data/dns_rules/DnsRulesDataSource.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/data/dns_rules/DnsRulesDataSource.kt new file mode 100644 index 000000000..e8d108006 --- /dev/null +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/data/dns_rules/DnsRulesDataSource.kt @@ -0,0 +1,84 @@ +/* + This file is part of InviZible Pro. + + InviZible Pro is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + InviZible Pro is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with InviZible Pro. If not, see . + + Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com + */ + +package pan.alexander.tordnscrypt.data.dns_rules + +import java.io.InputStreamReader + +interface DnsRulesDataSource { + fun getBlacklistRulesStream(): InputStreamReader + fun getSingleBlacklistRulesStream(): InputStreamReader? + fun saveSingleBlacklistRules(rules: List) + fun getRemoteBlacklistRulesStream(): InputStreamReader + fun getRemoteBlacklistRulesFileSize(): Long + fun getRemoteBlacklistRulesFileDate(): Long + fun clearRemoteBlacklistRules() + fun getLocalBlacklistRulesStream(): InputStreamReader + fun clearLocalBlacklistRules() + fun getLocalBlacklistRulesFileSize(): Long + fun getLocalBlacklistRulesFileDate(): Long + + fun getWhitelistRulesStream(): InputStreamReader + fun getSingleWhitelistRulesStream(): InputStreamReader? + fun saveSingleWhitelistRules(rules: List) + fun getRemoteWhitelistRulesStream(): InputStreamReader + fun getRemoteWhitelistRulesFileSize(): Long + fun getRemoteWhitelistRulesFileDate(): Long + fun clearRemoteWhitelistRules() + fun getLocalWhitelistRulesStream(): InputStreamReader + fun getLocaleWhitelistRulesFileSize(): Long + fun getLocalWhitelistRulesFileDate(): Long + fun clearLocalWhitelistRules() + + fun getIpBlacklistRulesStream(): InputStreamReader + fun getSingleIpBlacklistRulesStream(): InputStreamReader? + fun saveSingleIpBlacklistRules(rules: List) + fun getRemoteIpBlacklistRulesStream(): InputStreamReader + fun getRemoteIpBlacklistRulesFileSize(): Long + fun getRemoteIpBlacklistRulesFileDate(): Long + fun clearRemoteIpBlacklistRules() + fun getLocalIpBlacklistRulesStream(): InputStreamReader + fun getLocalIpBlacklistRulesFileSize(): Long + fun getLocalIpBlacklistRulesFileDate(): Long + fun clearLocalIpBlacklistRules() + + fun getForwardingRulesStream(): InputStreamReader + fun getSingleForwardingRulesStream(): InputStreamReader? + fun saveSingleForwardingRules(rules: List) + fun getRemoteForwardingRulesStream(): InputStreamReader + fun getRemoteForwardingRulesFileSize(): Long + fun getRemoteForwardingRulesFileDate(): Long + fun clearRemoteForwardingRules() + fun getLocalForwardingRulesStream(): InputStreamReader + fun getLocalForwardingRulesFileSize(): Long + fun getLocalForwardingRulesFileDate(): Long + fun clearLocalForwardingRules() + + fun getCloakingRulesStream(): InputStreamReader + fun getSingleCloakingRulesStream(): InputStreamReader? + fun saveSingleCloakingRules(rules: List) + fun getRemoteCloakingRulesStream(): InputStreamReader + fun getRemoteCloakingRulesFileSize(): Long + fun getRemoteCloakingRulesFileDate(): Long + fun clearRemoteCloakingRules() + fun getLocalCloakingRulesStream(): InputStreamReader + fun getLocalCloakingRulesFileSize(): Long + fun getLocalCloakingRulesFileDate(): Long + fun clearLocalCloakingRules() +} diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/data/dns_rules/DnsRulesDataSourceImpl.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/data/dns_rules/DnsRulesDataSourceImpl.kt new file mode 100644 index 000000000..b678e5806 --- /dev/null +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/data/dns_rules/DnsRulesDataSourceImpl.kt @@ -0,0 +1,309 @@ +/* + This file is part of InviZible Pro. + + InviZible Pro is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + InviZible Pro is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with InviZible Pro. If not, see . + + Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com + */ + +package pan.alexander.tordnscrypt.data.dns_rules + +import android.content.Context +import pan.alexander.tordnscrypt.settings.PathVars +import pan.alexander.tordnscrypt.utils.filemanager.FileManager +import java.io.File +import java.io.InputStreamReader +import javax.inject.Inject + +class DnsRulesDataSourceImpl @Inject constructor( + private val context: Context, + private val pathVars: PathVars +) : DnsRulesDataSource { + + override fun getBlacklistRulesStream(): InputStreamReader { + return getInputStreamFromFile(pathVars.dnsCryptBlackListPath) + } + + override fun getSingleBlacklistRulesStream(): InputStreamReader? { + val file = File(pathVars.dnsCryptSingleBlackListPath) + if (file.isFile) { + return file.reader() + } + return null + } + + override fun saveSingleBlacklistRules(rules: List) { + FileManager.writeTextFileSynchronous(context, pathVars.dnsCryptSingleBlackListPath, rules) + } + + override fun getRemoteBlacklistRulesStream(): InputStreamReader { + return getInputStreamFromFile(pathVars.dnsCryptRemoteBlackListPath) + } + + override fun getRemoteBlacklistRulesFileSize(): Long { + return File(pathVars.dnsCryptRemoteBlackListPath).length() + } + + override fun getRemoteBlacklistRulesFileDate(): Long { + return File(pathVars.dnsCryptRemoteBlackListPath).lastModified() + } + + override fun clearRemoteBlacklistRules() { + File(pathVars.dnsCryptRemoteBlackListPath).printWriter().use { + println() + } + } + + override fun getLocalBlacklistRulesStream(): InputStreamReader { + return getInputStreamFromFile(pathVars.dnsCryptLocalBlackListPath) + } + + override fun clearLocalBlacklistRules() { + File(pathVars.dnsCryptLocalBlackListPath).printWriter().use { + println() + } + } + + override fun getLocalBlacklistRulesFileSize(): Long { + return File(pathVars.dnsCryptLocalBlackListPath).length() + } + + override fun getLocalBlacklistRulesFileDate(): Long { + return File(pathVars.dnsCryptLocalBlackListPath).lastModified() + } + + override fun getWhitelistRulesStream(): InputStreamReader { + return getInputStreamFromFile(pathVars.dnsCryptWhiteListPath) + } + + override fun getSingleWhitelistRulesStream(): InputStreamReader? { + val file = File(pathVars.dnsCryptSingleWhiteListPath) + if (file.isFile) { + return file.reader() + } + return null + } + + override fun saveSingleWhitelistRules(rules: List) { + FileManager.writeTextFileSynchronous(context, pathVars.dnsCryptSingleWhiteListPath, rules) + } + + override fun getRemoteWhitelistRulesStream(): InputStreamReader { + return getInputStreamFromFile(pathVars.dnsCryptRemoteWhiteListPath) + } + + override fun getRemoteWhitelistRulesFileSize(): Long { + return File(pathVars.dnsCryptRemoteWhiteListPath).length() + } + + override fun getRemoteWhitelistRulesFileDate(): Long { + return File(pathVars.dnsCryptRemoteWhiteListPath).lastModified() + } + + override fun clearRemoteWhitelistRules() { + File(pathVars.dnsCryptRemoteWhiteListPath).printWriter().use { + println() + } + } + + override fun getLocalWhitelistRulesStream(): InputStreamReader { + return getInputStreamFromFile(pathVars.dnsCryptLocalWhiteListPath) + } + + override fun getLocaleWhitelistRulesFileSize(): Long { + return File(pathVars.dnsCryptLocalWhiteListPath).length() + } + + override fun getLocalWhitelistRulesFileDate(): Long { + return File(pathVars.dnsCryptLocalWhiteListPath).lastModified() + } + + override fun clearLocalWhitelistRules() { + File(pathVars.dnsCryptLocalWhiteListPath).printWriter().use { + println() + } + } + + override fun getIpBlacklistRulesStream(): InputStreamReader { + return getInputStreamFromFile(pathVars.dnsCryptIPBlackListPath) + } + + override fun getSingleIpBlacklistRulesStream(): InputStreamReader? { + val file = File(pathVars.dnsCryptSingleIPBlackListPath) + if (file.isFile) { + return file.reader() + } + return null + } + + override fun saveSingleIpBlacklistRules(rules: List) { + FileManager.writeTextFileSynchronous(context, pathVars.dnsCryptSingleIPBlackListPath, rules) + } + + override fun getRemoteIpBlacklistRulesStream(): InputStreamReader { + return getInputStreamFromFile(pathVars.dnsCryptRemoteIPBlackListPath) + } + + override fun getRemoteIpBlacklistRulesFileSize(): Long { + return File(pathVars.dnsCryptRemoteIPBlackListPath).length() + } + + override fun getRemoteIpBlacklistRulesFileDate(): Long { + return File(pathVars.dnsCryptRemoteIPBlackListPath).lastModified() + } + + override fun clearRemoteIpBlacklistRules() { + File(pathVars.dnsCryptRemoteIPBlackListPath).printWriter().use { + println() + } + } + + override fun getLocalIpBlacklistRulesStream(): InputStreamReader { + return getInputStreamFromFile(pathVars.dnsCryptLocalIPBlackListPath) + } + + override fun getLocalIpBlacklistRulesFileSize(): Long { + return File(pathVars.dnsCryptLocalIPBlackListPath).length() + } + + override fun getLocalIpBlacklistRulesFileDate(): Long { + return File(pathVars.dnsCryptLocalIPBlackListPath).lastModified() + } + + override fun clearLocalIpBlacklistRules() { + File(pathVars.dnsCryptLocalIPBlackListPath).printWriter().use { + println() + } + } + + override fun getForwardingRulesStream(): InputStreamReader { + return getInputStreamFromFile(pathVars.dnsCryptForwardingRulesPath) + } + + override fun getSingleForwardingRulesStream(): InputStreamReader? { + val file = File(pathVars.dnsCryptSingleForwardingRulesPath) + if (file.isFile) { + return file.reader() + } + return null + } + + override fun saveSingleForwardingRules(rules: List) { + FileManager.writeTextFileSynchronous( + context, + pathVars.dnsCryptSingleForwardingRulesPath, + rules + ) + } + + override fun getRemoteForwardingRulesStream(): InputStreamReader { + return getInputStreamFromFile(pathVars.dnsCryptRemoteForwardingRulesPath) + } + + override fun getRemoteForwardingRulesFileSize(): Long { + return File(pathVars.dnsCryptRemoteForwardingRulesPath).length() + } + + override fun getRemoteForwardingRulesFileDate(): Long { + return File(pathVars.dnsCryptRemoteForwardingRulesPath).lastModified() + } + + override fun clearRemoteForwardingRules() { + File(pathVars.dnsCryptRemoteForwardingRulesPath).printWriter().use { + println() + } + } + + override fun getLocalForwardingRulesStream(): InputStreamReader { + return getInputStreamFromFile(pathVars.dnsCryptLocalForwardingRulesPath) + } + + override fun getLocalForwardingRulesFileSize(): Long { + return File(pathVars.dnsCryptLocalForwardingRulesPath).length() + } + + override fun getLocalForwardingRulesFileDate(): Long { + return File(pathVars.dnsCryptLocalForwardingRulesPath).lastModified() + } + + override fun clearLocalForwardingRules() { + File(pathVars.dnsCryptLocalForwardingRulesPath).printWriter().use { + println() + } + } + + override fun getCloakingRulesStream(): InputStreamReader { + return getInputStreamFromFile(pathVars.dnsCryptCloakingRulesPath) + } + + override fun getSingleCloakingRulesStream(): InputStreamReader? { + val file = File(pathVars.dnsCryptSingleCloakingRulesPath) + if (file.isFile) { + return file.reader() + } + return null + } + + override fun saveSingleCloakingRules(rules: List) { + FileManager.writeTextFileSynchronous( + context, + pathVars.dnsCryptSingleCloakingRulesPath, + rules + ) + } + + override fun getRemoteCloakingRulesStream(): InputStreamReader { + return getInputStreamFromFile(pathVars.dnsCryptRemoteCloakingRulesPath) + } + + override fun getRemoteCloakingRulesFileSize(): Long { + return File(pathVars.dnsCryptRemoteCloakingRulesPath).length() + } + + override fun getRemoteCloakingRulesFileDate(): Long { + return File(pathVars.dnsCryptRemoteCloakingRulesPath).lastModified() + } + + override fun clearRemoteCloakingRules() { + File(pathVars.dnsCryptRemoteCloakingRulesPath).printWriter().use { + println() + } + } + + override fun getLocalCloakingRulesStream(): InputStreamReader { + return getInputStreamFromFile(pathVars.dnsCryptLocalCloakingRulesPath) + } + + override fun getLocalCloakingRulesFileSize(): Long { + return File(pathVars.dnsCryptLocalCloakingRulesPath).length() + } + + override fun getLocalCloakingRulesFileDate(): Long { + return File(pathVars.dnsCryptLocalCloakingRulesPath).lastModified() + } + + override fun clearLocalCloakingRules() { + File(pathVars.dnsCryptLocalCloakingRulesPath).printWriter().use { + println() + } + } + + private fun getInputStreamFromFile(path: String): InputStreamReader { + val file = File(path) + if (!file.isFile) { + file.createNewFile() + } + return file.reader() + } +} diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/data/dns_rules/DnsRulesRepositoryImpl.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/data/dns_rules/DnsRulesRepositoryImpl.kt new file mode 100644 index 000000000..4b4e7f95e --- /dev/null +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/data/dns_rules/DnsRulesRepositoryImpl.kt @@ -0,0 +1,431 @@ +/* + This file is part of InviZible Pro. + + InviZible Pro is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + InviZible Pro is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with InviZible Pro. If not, see . + + Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com + */ + +package pan.alexander.tordnscrypt.data.dns_rules + +import kotlinx.coroutines.isActive +import pan.alexander.tordnscrypt.domain.dns_rules.DnsRulesMetadata +import pan.alexander.tordnscrypt.domain.dns_rules.DnsRulesRepository +import pan.alexander.tordnscrypt.domain.dns_rules.DnsRulesRepository.Companion.LOCAL_RULES_DEFAULT_HEADER +import pan.alexander.tordnscrypt.domain.dns_rules.DnsRulesRepository.Companion.REMOTE_RULES_DEFAULT_HEADER +import pan.alexander.tordnscrypt.settings.PathVars +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.recycler.DnsRuleRecycleItem +import pan.alexander.tordnscrypt.utils.Utils.getDomainNameFromUrl +import java.io.BufferedReader +import java.io.InputStreamReader +import java.util.Date +import java.util.regex.Pattern +import javax.inject.Inject +import kotlin.coroutines.coroutineContext + +class DnsRulesRepositoryImpl @Inject constructor( + private val dataSource: DnsRulesDataSource, + pathVars: PathVars +) : DnsRulesRepository { + + private val forwardingDefaultRule = pathVars.dnsCryptDefaultForwardingRule + .substringBefore(" ") + private val cloakingDefaultRule = pathVars.dnsCryptDefaultCloakingRule + .substringBefore(" ") + + override suspend fun getMixedBlacklistRulesMetadata(): DnsRulesMetadata.MixedDnsRulesMetadata = + DnsRulesMetadata.MixedDnsRulesMetadata( + getRulesCountFromFile(dataSource.getBlacklistRulesStream()) + ) + + override suspend fun getSingleBlacklistRules(): List = + getSingleRulesFromFile( + dataSource.getSingleBlacklistRulesStream() ?: run { + val rules = if (getMixedBlacklistRulesMetadata().count > 0 + && getLocalBlacklistRulesMetadata().count == 0 + ) { + getSingleRulesFromFile(dataSource.getBlacklistRulesStream()).also { + clearLocalBlacklistRules() + } + } else { + emptyList() + } + saveSingleBlacklistRules(rules) + dataSource.getSingleBlacklistRulesStream() ?: throw IllegalStateException( + "DnsRulesRepository getSingleBlacklistRules" + ) + } + ) + + override fun saveSingleBlacklistRules(rules: List) { + dataSource.saveSingleBlacklistRules(mapSingleRulesToLines(rules)) + } + + override suspend fun getRemoteBlacklistRulesMetadata(): DnsRulesMetadata.RemoteDnsRulesMetadata { + val url = getHeaderFromFile(dataSource.getRemoteBlacklistRulesStream()) + ?: REMOTE_RULES_DEFAULT_HEADER + return DnsRulesMetadata.RemoteDnsRulesMetadata( + name = getDomainNameFromUrl(url), + url = url, + date = Date(dataSource.getRemoteBlacklistRulesFileDate()), + count = getRulesCountFromFile(dataSource.getRemoteBlacklistRulesStream()), + size = dataSource.getRemoteBlacklistRulesFileSize() + ) + } + + override fun clearRemoteBlacklistRules() { + dataSource.clearRemoteBlacklistRules() + } + + override fun clearLocalBlacklistRules() { + dataSource.clearLocalBlacklistRules() + } + + override suspend fun getLocalBlacklistRulesMetadata(): DnsRulesMetadata.LocalDnsRulesMetadata = + DnsRulesMetadata.LocalDnsRulesMetadata( + name = getHeaderFromFile(dataSource.getLocalBlacklistRulesStream()) + ?: LOCAL_RULES_DEFAULT_HEADER, + date = Date(dataSource.getLocalBlacklistRulesFileDate()), + count = getRulesCountFromFile(dataSource.getLocalBlacklistRulesStream()), + size = dataSource.getLocalBlacklistRulesFileSize() + ) + + override suspend fun getMixedWhitelistRulesMetadata(): DnsRulesMetadata.MixedDnsRulesMetadata = + DnsRulesMetadata.MixedDnsRulesMetadata( + getRulesCountFromFile(dataSource.getWhitelistRulesStream()) + ) + + override suspend fun getSingleWhitelistRules(): List = + getSingleRulesFromFile( + dataSource.getSingleWhitelistRulesStream() ?: run { + val rules = if (getMixedWhitelistRulesMetadata().count > 0 + && getLocalWhitelistRulesMetadata().count == 0 + ) { + getSingleRulesFromFile(dataSource.getWhitelistRulesStream()).also { + clearLocalWhitelistRules() + } + } else { + emptyList() + } + saveSingleWhitelistRules(rules) + dataSource.getSingleWhitelistRulesStream() ?: throw IllegalStateException( + "DnsRulesRepository getSingleWhitelistRules" + ) + } + ) + + override fun saveSingleWhitelistRules(rules: List) { + dataSource.saveSingleWhitelistRules(mapSingleRulesToLines(rules)) + } + + override suspend fun getRemoteWhitelistRulesMetadata(): DnsRulesMetadata.RemoteDnsRulesMetadata { + val url = getHeaderFromFile(dataSource.getRemoteWhitelistRulesStream()) + ?: REMOTE_RULES_DEFAULT_HEADER + return DnsRulesMetadata.RemoteDnsRulesMetadata( + name = getDomainNameFromUrl(url), + url = url, + date = Date(dataSource.getRemoteWhitelistRulesFileDate()), + count = getRulesCountFromFile(dataSource.getRemoteWhitelistRulesStream()), + size = dataSource.getRemoteWhitelistRulesFileSize() + ) + } + + override fun clearRemoteWhitelistRules() { + dataSource.clearRemoteWhitelistRules() + } + + override fun clearLocalWhitelistRules() { + dataSource.clearLocalWhitelistRules() + } + + override suspend fun getLocalWhitelistRulesMetadata(): DnsRulesMetadata.LocalDnsRulesMetadata = + DnsRulesMetadata.LocalDnsRulesMetadata( + name = getHeaderFromFile(dataSource.getLocalWhitelistRulesStream()) + ?: LOCAL_RULES_DEFAULT_HEADER, + date = Date(dataSource.getLocalWhitelistRulesFileDate()), + count = getRulesCountFromFile(dataSource.getLocalWhitelistRulesStream()), + size = dataSource.getLocaleWhitelistRulesFileSize() + ) + + override suspend fun getMixedIpBlacklistRulesMetadata(): DnsRulesMetadata.MixedDnsRulesMetadata = + DnsRulesMetadata.MixedDnsRulesMetadata( + getRulesCountFromFile(dataSource.getIpBlacklistRulesStream()) + ) + + override suspend fun getSingleIpBlacklistRules(): List = + getSingleRulesFromFile( + dataSource.getSingleIpBlacklistRulesStream() ?: run { + val rules = if (getMixedIpBlacklistRulesMetadata().count > 0 + && getLocalIpBlacklistRulesMetadata().count == 0 + ) { + getSingleRulesFromFile(dataSource.getIpBlacklistRulesStream()).also { + clearLocalIpBlacklistRules() + } + } else { + emptyList() + } + saveSingleIpBlacklistRules(rules) + dataSource.getSingleIpBlacklistRulesStream() ?: throw IllegalStateException( + "DnsRulesRepository getSingleIpBlacklistRules" + ) + } + ) + + override fun saveSingleIpBlacklistRules(rules: List) { + dataSource.saveSingleIpBlacklistRules(mapSingleRulesToLines(rules)) + } + + override suspend fun getRemoteIpBlacklistRulesMetadata(): DnsRulesMetadata.RemoteDnsRulesMetadata { + val url = getHeaderFromFile(dataSource.getRemoteIpBlacklistRulesStream()) + ?: REMOTE_RULES_DEFAULT_HEADER + return DnsRulesMetadata.RemoteDnsRulesMetadata( + name = getDomainNameFromUrl(url), + url = url, + date = Date(dataSource.getRemoteIpBlacklistRulesFileDate()), + count = getRulesCountFromFile(dataSource.getRemoteIpBlacklistRulesStream()), + size = dataSource.getRemoteIpBlacklistRulesFileSize() + ) + } + + override fun clearRemoteIpBlacklistRules() { + dataSource.clearRemoteIpBlacklistRules() + } + + override fun clearLocalIpBlacklistRules() { + dataSource.clearLocalIpBlacklistRules() + } + + override suspend fun getLocalIpBlacklistRulesMetadata(): DnsRulesMetadata.LocalDnsRulesMetadata = + DnsRulesMetadata.LocalDnsRulesMetadata( + name = getHeaderFromFile(dataSource.getLocalIpBlacklistRulesStream()) + ?: LOCAL_RULES_DEFAULT_HEADER, + date = Date(dataSource.getLocalIpBlacklistRulesFileDate()), + count = getRulesCountFromFile(dataSource.getLocalIpBlacklistRulesStream()), + size = dataSource.getLocalIpBlacklistRulesFileSize() + ) + + override suspend fun getMixedForwardingRulesMetadata(): DnsRulesMetadata.MixedDnsRulesMetadata = + DnsRulesMetadata.MixedDnsRulesMetadata( + getRulesCountFromFile(dataSource.getForwardingRulesStream()) + ) + + override suspend fun getSingleForwardingRules(): List = + getSingleRulesFromFile( + dataSource.getSingleForwardingRulesStream() ?: run { + val rules = if (getMixedForwardingRulesMetadata().count > 0 + && getLocalForwardingRulesMetadata().count == 1 + ) { + getSingleRulesFromFile(dataSource.getForwardingRulesStream()).also { + clearLocalForwardingRules() + } + } else { + listOf( + DnsRuleRecycleItem.DnsSingleRule( + rule = forwardingDefaultRule, + protected = true, + active = true + ) + ) + } + saveSingleForwardingRules(rules) + dataSource.getSingleForwardingRulesStream() ?: throw IllegalStateException( + "DnsRulesRepository getSingleForwardingRules" + ) + } + ) + + override fun saveSingleForwardingRules(rules: List) { + dataSource.saveSingleForwardingRules(mapSingleRulesToLines(rules)) + } + + override suspend fun getRemoteForwardingRulesMetadata(): DnsRulesMetadata.RemoteDnsRulesMetadata { + val url = getHeaderFromFile(dataSource.getRemoteForwardingRulesStream()) + ?: REMOTE_RULES_DEFAULT_HEADER + return DnsRulesMetadata.RemoteDnsRulesMetadata( + name = getDomainNameFromUrl(url), + url = url, + date = Date(dataSource.getRemoteForwardingRulesFileDate()), + count = getRulesCountFromFile(dataSource.getRemoteForwardingRulesStream()), + size = dataSource.getRemoteForwardingRulesFileSize() + ) + } + + override fun clearRemoteForwardingRules() { + dataSource.clearRemoteForwardingRules() + } + + override fun clearLocalForwardingRules() { + dataSource.clearLocalForwardingRules() + } + + override suspend fun getLocalForwardingRulesMetadata(): DnsRulesMetadata.LocalDnsRulesMetadata = + DnsRulesMetadata.LocalDnsRulesMetadata( + name = getHeaderFromFile(dataSource.getLocalForwardingRulesStream()) + ?: LOCAL_RULES_DEFAULT_HEADER, + date = Date(dataSource.getLocalForwardingRulesFileDate()), + count = getRulesCountFromFile(dataSource.getLocalForwardingRulesStream()), + size = dataSource.getLocalForwardingRulesFileSize() + ) + + override suspend fun getMixedCloakingRulesMetadata(): DnsRulesMetadata.MixedDnsRulesMetadata = + DnsRulesMetadata.MixedDnsRulesMetadata( + getRulesCountFromFile(dataSource.getCloakingRulesStream()) + ) + + override suspend fun getSingleCloakingRules(): List = + getSingleRulesFromFile( + dataSource.getSingleCloakingRulesStream() ?: run { + val rules = if (getMixedCloakingRulesMetadata().count > 0 + && getLocalCloakingRulesMetadata().count == 1 + ) { + getSingleRulesFromFile(dataSource.getCloakingRulesStream()).also { + clearLocalCloakingRules() + } + } else { + listOf( + DnsRuleRecycleItem.DnsSingleRule( + rule = cloakingDefaultRule, + protected = true, + active = true + ) + ) + } + saveSingleCloakingRules(rules) + dataSource.getSingleCloakingRulesStream() ?: throw IllegalStateException( + "DnsRulesRepository getSingleCloakingRules" + ) + } + ) + + override fun saveSingleCloakingRules(rules: List) { + dataSource.saveSingleCloakingRules(mapSingleRulesToLines(rules)) + } + + override suspend fun getRemoteCloakingRulesMetadata(): DnsRulesMetadata.RemoteDnsRulesMetadata { + val url = getHeaderFromFile(dataSource.getRemoteCloakingRulesStream()) + ?: REMOTE_RULES_DEFAULT_HEADER + return DnsRulesMetadata.RemoteDnsRulesMetadata( + name = getDomainNameFromUrl(url), + url = url, + date = Date(dataSource.getRemoteCloakingRulesFileDate()), + count = getRulesCountFromFile(dataSource.getRemoteCloakingRulesStream()), + size = dataSource.getRemoteCloakingRulesFileSize() + ) + } + + override fun clearRemoteCloakingRules() { + dataSource.clearRemoteCloakingRules() + } + + override fun clearLocalCloakingRules() { + dataSource.clearLocalCloakingRules() + } + + override suspend fun getLocalCloakingRulesMetadata(): DnsRulesMetadata.LocalDnsRulesMetadata = + DnsRulesMetadata.LocalDnsRulesMetadata( + name = getHeaderFromFile(dataSource.getLocalCloakingRulesStream()) + ?: LOCAL_RULES_DEFAULT_HEADER, + date = Date(dataSource.getLocalCloakingRulesFileDate()), + count = getRulesCountFromFile(dataSource.getLocalCloakingRulesStream()), + size = dataSource.getLocalCloakingRulesFileSize() + ) + + private fun mapLineToSingleRule(line: String): DnsRuleRecycleItem.DnsSingleRule { + val active = !line.startsWith("#") + val protected = if (active) { + line.startsWith(forwardingDefaultRule.substringBefore(" ")) + || line.startsWith(cloakingDefaultRule.substringBefore(" ")) + } else { + line.startsWith("#${forwardingDefaultRule.substringBefore(" ")}") + || line.startsWith("#${cloakingDefaultRule.substringBefore(" ")}") + } + return DnsRuleRecycleItem.DnsSingleRule( + line.removePrefix("#"), + protected, + active + ) + } + + private fun mapSingleRulesToLines(rules: List): List { + val lines = mutableListOf() + for (rule in rules) { + + if (rule.rule.isEmpty()) { + continue + } + + val line = if (rule.active) { + rule.rule + } else { + "#${rule.rule}" + } + lines.add(line) + } + return lines + } + + private suspend fun getHeaderFromFile(inputReader: InputStreamReader): String? { + val namePattern = Pattern.compile("^# ([^#]+) #$") + BufferedReader(inputReader).use { reader -> + var line = reader.readLine() + while (line != null && coroutineContext.isActive) { + if (line.startsWith("#")) { + val matcher = namePattern.matcher(line) + if (matcher.matches()) { + matcher.group(1)?.let { + return it + } + } + } else if (line.isNotEmpty()) { + return null + } + line = reader.readLine() + } + } + return null + } + + private suspend fun getRulesCountFromFile(inputReader: InputStreamReader): Int { + var count = 0 + BufferedReader(inputReader).use { reader -> + var line = reader.readLine() + while (line != null && coroutineContext.isActive) { + if (line.isNotEmpty() && !line.startsWith("#")) { + count++ + } + line = reader.readLine() + } + } + return count + } + + private suspend fun getSingleRulesFromFile( + inputReader: InputStreamReader + ): List { + val rules = mutableListOf() + BufferedReader(inputReader).use { reader -> + var line = reader.readLine() + while (line != null && coroutineContext.isActive) { + if (line.isNotEmpty() + && !line.startsWith("##") + && !(line.startsWith("#") && line.endsWith("#")) + ) { + rules += mapLineToSingleRule(line) + } + line = reader.readLine() + } + } + return rules + } +} diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/AppComponent.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/AppComponent.kt index bcc33d221..60ed98e69 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/AppComponent.kt +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/AppComponent.kt @@ -49,8 +49,12 @@ import pan.alexander.tordnscrypt.proxy.ProxyFragment import pan.alexander.tordnscrypt.settings.* import pan.alexander.tordnscrypt.settings.dnscrypt_relays.PreferencesDNSCryptRelays import pan.alexander.tordnscrypt.settings.dnscrypt_servers.PreferencesDNSCryptServers +import pan.alexander.tordnscrypt.settings.dnscrypt_settings.RulesEraser import pan.alexander.tordnscrypt.settings.dnscrypt_settings.PreferencesDNSFragment import pan.alexander.tordnscrypt.settings.firewall.FirewallFragment +import pan.alexander.tordnscrypt.settings.itpd_settings.PreferencesITPDFragment +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.DnsRulesFragment +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.UpdateLocalDnsRulesWorker import pan.alexander.tordnscrypt.settings.tor_apps.UnlockTorAppsFragment import pan.alexander.tordnscrypt.settings.tor_bridges.BridgeAdapter import pan.alexander.tordnscrypt.settings.tor_bridges.PreferencesTorBridges @@ -67,6 +71,9 @@ import pan.alexander.tordnscrypt.utils.filemanager.FileManager import pan.alexander.tordnscrypt.utils.integrity.Verifier import pan.alexander.tordnscrypt.utils.root.RootExecService import pan.alexander.tordnscrypt.utils.web.TorRefreshIPsWork +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteDnsRulesWorker +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.existing.RemixExistingDnsRulesWorker +import pan.alexander.tordnscrypt.settings.itpd_settings.ITPDSubscriptionsFragment import pan.alexander.tordnscrypt.vpn.service.ServiceVPNHandler import javax.inject.Singleton @@ -114,8 +121,10 @@ interface AppComponent { fun inject(fragment: PreferencesITPDFragment) fun inject(fragment: PreferencesDNSCryptRelays) fun inject(fragment: PreferencesDNSFragment) + fun inject(fragment: ITPDSubscriptionsFragment) fun inject(fragment: UpdateModulesDialogFragment) fun inject(fragment: NotificationHelper) + fun inject(fragment: DnsRulesFragment) fun inject(service: ModulesService) fun inject(service: RootExecService) fun inject(service: UpdateService) @@ -150,4 +159,8 @@ interface AppComponent { fun inject(installer: Installer) fun inject(installedApplicationsManager: InstalledApplicationsManager) fun inject(agreementDialog: AgreementDialog) + fun inject(rulesEraser: RulesEraser) + fun inject(worker: UpdateRemoteDnsRulesWorker) + fun inject(worker: UpdateLocalDnsRulesWorker) + fun inject(worker: RemixExistingDnsRulesWorker) } diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/DataSourcesModule.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/DataSourcesModule.kt index 19d6f84dc..5efcf8e19 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/DataSourcesModule.kt +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/DataSourcesModule.kt @@ -26,6 +26,8 @@ import pan.alexander.tordnscrypt.data.connection_checker.ConnectionCheckerDataSo import pan.alexander.tordnscrypt.data.connection_checker.ConnectionCheckerDataSourceImpl import pan.alexander.tordnscrypt.data.dns_resolver.DnsDataSource import pan.alexander.tordnscrypt.data.dns_resolver.DnsDataSourceImpl +import pan.alexander.tordnscrypt.data.dns_rules.DnsRulesDataSource +import pan.alexander.tordnscrypt.data.dns_rules.DnsRulesDataSourceImpl import pan.alexander.tordnscrypt.data.preferences.PreferenceDataSource import pan.alexander.tordnscrypt.data.preferences.PreferenceDataSourceImpl @@ -60,4 +62,9 @@ abstract class DataSourcesModule { abstract fun provideBridgesCountriesDataSource( bridgesCountriesDataSource: BridgesCountriesDataSourceImpl ): BridgesCountriesDataSource + + @Binds + abstract fun provideDnsRulesDataSource( + dnsDataSource: DnsRulesDataSourceImpl + ): DnsRulesDataSource } diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/InteractorsModule.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/InteractorsModule.kt index ab72ec851..16344ce3d 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/InteractorsModule.kt +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/InteractorsModule.kt @@ -25,6 +25,8 @@ import pan.alexander.tordnscrypt.domain.connection_checker.ConnectionCheckerInte import pan.alexander.tordnscrypt.domain.connection_checker.ConnectionCheckerInteractorImpl import pan.alexander.tordnscrypt.domain.dns_resolver.DnsInteractor import pan.alexander.tordnscrypt.domain.dns_resolver.DnsInteractorImpl +import pan.alexander.tordnscrypt.domain.dns_rules.DnsRulesInteractor +import pan.alexander.tordnscrypt.domain.dns_rules.DnsRulesInteractorImpl import pan.alexander.tordnscrypt.domain.tor_ips.TorIpsInteractor import pan.alexander.tordnscrypt.domain.tor_ips.TorIpsInteractorImpl @@ -43,4 +45,9 @@ abstract class InteractorsModule { abstract fun provideTorIpsInteractor( torIpsInteractor: TorIpsInteractorImpl ): TorIpsInteractor + + @Binds + abstract fun provideDnsRulesInteractor( + dnsRulesInteractor: DnsRulesInteractorImpl + ): DnsRulesInteractor } diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/RepositoryModule.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/RepositoryModule.kt index e0d499a9a..7417c42ba 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/RepositoryModule.kt +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/RepositoryModule.kt @@ -26,6 +26,7 @@ import pan.alexander.tordnscrypt.data.bridges.DefaultVanillaBridgeRepositoryImpl import pan.alexander.tordnscrypt.data.bridges.RequestBridgesRepositoryImpl import pan.alexander.tordnscrypt.data.connection_checker.ConnectionCheckerRepositoryImpl import pan.alexander.tordnscrypt.data.dns_resolver.DnsRepositoryImpl +import pan.alexander.tordnscrypt.data.dns_rules.DnsRulesRepositoryImpl import pan.alexander.tordnscrypt.data.preferences.PreferenceRepositoryImpl import pan.alexander.tordnscrypt.data.resources.ResourceRepositoryImpl import pan.alexander.tordnscrypt.domain.bridges.BridgesCountriesRepository @@ -33,6 +34,7 @@ import pan.alexander.tordnscrypt.domain.bridges.DefaultVanillaBridgeRepository import pan.alexander.tordnscrypt.domain.bridges.RequestBridgesRepository import pan.alexander.tordnscrypt.domain.connection_checker.ConnectionCheckerRepository import pan.alexander.tordnscrypt.domain.dns_resolver.DnsRepository +import pan.alexander.tordnscrypt.domain.dns_rules.DnsRulesRepository import pan.alexander.tordnscrypt.domain.preferences.PreferenceRepository import pan.alexander.tordnscrypt.domain.resources.ResourceRepository @@ -69,4 +71,9 @@ abstract class RepositoryModule { abstract fun provideBridgesCountriesRepository( bridgesCountriesRepository: BridgesCountriesRepositoryImpl ): BridgesCountriesRepository + + @Binds + abstract fun provideDnsRulesRepository( + dnsRulesRepository: DnsRulesRepositoryImpl + ): DnsRulesRepository } diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/ViewModelModule.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/ViewModelModule.kt index 16c51faba..d601764bc 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/ViewModelModule.kt +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/ViewModelModule.kt @@ -30,6 +30,8 @@ import pan.alexander.tordnscrypt.TopFragmentViewModel import pan.alexander.tordnscrypt.settings.dnscrypt_relays.DnsRelayViewModel import pan.alexander.tordnscrypt.settings.dnscrypt_servers.DnsServerViewModel import pan.alexander.tordnscrypt.settings.firewall.FirewallViewModel +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.DnsRulesViewModel +import pan.alexander.tordnscrypt.settings.itpd_settings.ItpdSubscriptionsViewModel import pan.alexander.tordnscrypt.settings.tor_bridges.PreferencesTorBridgesViewModel import pan.alexander.tordnscrypt.settings.tor_ips.UnlockTorIpsViewModel @@ -84,4 +86,18 @@ abstract class ViewModelModule { abstract fun provideDnsRelayViewModel( dnsRelayViewModel: DnsRelayViewModel ): ViewModel + + @Binds + @IntoMap + @ViewModelKey(DnsRulesViewModel::class) + abstract fun provideDnsRuleViewModel( + dnsRulesViewModel: DnsRulesViewModel + ): ViewModel + + @Binds + @IntoMap + @ViewModelKey(ItpdSubscriptionsViewModel::class) + abstract fun provideItpdSubscriptionsViewModel( + itpdSubscriptionsViewModel: ItpdSubscriptionsViewModel + ): ViewModel } diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dialogs/ExtendedDialogFragment.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dialogs/ExtendedDialogFragment.java index 385b5f54a..46a6d57c0 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dialogs/ExtendedDialogFragment.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dialogs/ExtendedDialogFragment.java @@ -27,6 +27,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.Looper; +import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -127,7 +128,7 @@ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { public void show(@NonNull FragmentManager manager, String tag) { try { showDialog(manager, tag); - } catch (IllegalStateException e) { + } catch (IllegalStateException | WindowManager.BadTokenException e) { logw("ExtendedDialogFragment show", e); if (handler == null) { diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dialogs/SelectBridgesTransportDialogFragment.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dialogs/SelectBridgesTransportDialogFragment.kt index f895af487..4766fd2d8 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dialogs/SelectBridgesTransportDialogFragment.kt +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dialogs/SelectBridgesTransportDialogFragment.kt @@ -36,6 +36,7 @@ import androidx.lifecycle.ViewModelProvider import kotlinx.coroutines.ExperimentalCoroutinesApi import pan.alexander.tordnscrypt.App import pan.alexander.tordnscrypt.R +import pan.alexander.tordnscrypt.databinding.SelectTorTransportBinding import pan.alexander.tordnscrypt.di.SharedPreferencesModule.Companion.DEFAULT_PREFERENCES_NAME import pan.alexander.tordnscrypt.settings.tor_bridges.PreferencesTorBridgesViewModel import pan.alexander.tordnscrypt.utils.logger.Logger.loge @@ -67,26 +68,21 @@ class SelectBridgesTransportDialogFragment : ExtendedDialogFragment() { @SuppressLint("InflateParams") override fun assignBuilder(): AlertDialog.Builder = AlertDialog.Builder(requireActivity()).apply { - val layoutInflater = - requireActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater - val view: View = try { - layoutInflater.inflate(R.layout.select_tor_transport, null) + val binding = try { + SelectTorTransportBinding.inflate(LayoutInflater.from(requireContext())) } catch (e: Exception) { loge("SelectBridgesTransportDialogFragment assignBuilder", e) throw e } - val rbgTorTransport = view.findViewById(R.id.rbgTorTransport) - val chbRequestIPv6Bridges = view.findViewById(R.id.chbRequestIPv6Bridges) - if (defaultPreferences.getBoolean(TOR_USE_IPV6, true)) { - chbRequestIPv6Bridges.visibility = VISIBLE + binding.chbRequestIPv6Bridges.visibility = VISIBLE } else { - chbRequestIPv6Bridges.visibility = GONE + binding.chbRequestIPv6Bridges.visibility = GONE } - setView(view) + setView(binding.root) setTitle(R.string.pref_fast_use_tor_bridges_transport_select) @@ -94,23 +90,21 @@ class SelectBridgesTransportDialogFragment : ExtendedDialogFragment() { okButtonPressed = true - val ipv6Bridges = chbRequestIPv6Bridges.isChecked - - when (rbgTorTransport.checkedRadioButtonId) { + when (binding.rbgTorTransport.checkedRadioButtonId) { R.id.rbObfsNone -> preferencesTorBridgesViewModel.requestTorBridgesCaptchaChallenge( - "0", - ipv6Bridges + "vanilla", + binding.chbRequestIPv6Bridges.isChecked ) R.id.rbObfs4 -> preferencesTorBridgesViewModel.requestTorBridgesCaptchaChallenge( "obfs4", - ipv6Bridges + binding.chbRequestIPv6Bridges.isChecked ) R.id.rbWebTunnel -> preferencesTorBridgesViewModel.requestTorBridgesCaptchaChallenge( "webtunnel", - ipv6Bridges + binding.chbRequestIPv6Bridges.isChecked ) } } diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dialogs/SendCrashReport.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dialogs/SendCrashReport.kt index e60afe0b0..03d09eda6 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dialogs/SendCrashReport.kt +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dialogs/SendCrashReport.kt @@ -21,6 +21,7 @@ package pan.alexander.tordnscrypt.dialogs import android.content.Context import android.os.Bundle +import androidx.annotation.Keep import androidx.appcompat.app.AlertDialog import androidx.core.content.FileProvider import androidx.preference.PreferenceManager @@ -41,6 +42,7 @@ import java.io.FileWriter import java.io.InputStreamReader import javax.inject.Inject +@Keep class SendCrashReport : ExtendedDialogFragment() { @Inject diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dialogs/progressDialogs/ImportRulesDialog.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dialogs/progressDialogs/ImportRulesDialog.java deleted file mode 100644 index d10f78b99..000000000 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dialogs/progressDialogs/ImportRulesDialog.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - This file is part of InviZible Pro. - - InviZible Pro is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - InviZible Pro is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with InviZible Pro. If not, see . - - Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com - */ - -package pan.alexander.tordnscrypt.dialogs.progressDialogs; - -import static pan.alexander.tordnscrypt.utils.logger.Logger.loge; - -import android.annotation.SuppressLint; -import android.app.Dialog; -import android.os.Bundle; -import android.view.View; -import android.widget.Button; -import android.widget.ProgressBar; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; - -import pan.alexander.tordnscrypt.R; -import pan.alexander.tordnscrypt.dialogs.ExtendedDialogFragment; -import pan.alexander.tordnscrypt.settings.dnscrypt_settings.ImportRules; - -public class ImportRulesDialog extends ExtendedDialogFragment implements ImportRules.OnDNSCryptRuleAddLineListener { - private Thread importThread; - private int linesAdded = 0; - private String dialogMessage; - private int buttonText = R.string.cancel; - private boolean dialogImportRulesIndeterminate; - private TextView tvDialogImportRules; - private ProgressBar pbDialogImportRules; - private Button btnDialogImportRules; - - public static ImportRulesDialog newInstance() { - return new ImportRulesDialog(); - } - - @SuppressLint("InflateParams") - @Override - public AlertDialog.Builder assignBuilder() { - - if (getActivity() == null) { - return null; - } - - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - builder.setTitle(R.string.import_dnscrypt_rules_dialog_title); - - View view; - try { - view = getActivity().getLayoutInflater().inflate(R.layout.dialog_import_dnscrypt_rules, null, false); - } catch (Exception e) { - loge("ImportRulesDialog assignBuilder", e); - throw e; - } - - if (view != null) { - builder.setView(view); - tvDialogImportRules = view.findViewById(R.id.tvDialogImportRules); - pbDialogImportRules = view.findViewById(R.id.pbDialogImportRules); - btnDialogImportRules = view.findViewById(R.id.btnDialogImportRules); - } - - if (btnDialogImportRules != null) { - btnDialogImportRules.setOnClickListener(v -> { - if (importThread != null && importThread.isAlive()) { - importThread.interrupt(); - } - this.dismiss(); - }); - } - - builder.setCancelable(false); - return builder; - } - - @NonNull - @Override - public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { - Dialog dialog = super.onCreateDialog(savedInstanceState); - dialog.setCanceledOnTouchOutside(false); - return dialog; - } - - @Override - public void onResume() { - super.onResume(); - - if (tvDialogImportRules != null && pbDialogImportRules != null && btnDialogImportRules != null) { - if (dialogMessage != null) { - tvDialogImportRules.setText(dialogMessage); - } - pbDialogImportRules.setIndeterminate(dialogImportRulesIndeterminate); - if (dialogImportRulesIndeterminate) { - pbDialogImportRules.setVisibility(View.VISIBLE); - } else { - pbDialogImportRules.setVisibility(View.GONE); - } - btnDialogImportRules.setText(buttonText); - } - } - - @Override - public void onDNSCryptRuleLinesAddingStarted(@NonNull Thread importThread) { - this.importThread = importThread; - - try { - int count = 0; - while (count < 15 && (tvDialogImportRules == null || pbDialogImportRules == null)) { - count++; - //noinspection BusyWait - Thread.sleep(100); - } - } catch (InterruptedException ignored){} - - if (handler != null) { - handler.post(() -> { - if (tvDialogImportRules != null && pbDialogImportRules != null) { - dialogMessage = String.format(getString(R.string.import_dnscrypt_rules_dialog_message), linesAdded); - tvDialogImportRules.setText(dialogMessage); - pbDialogImportRules.setVisibility(View.VISIBLE); - pbDialogImportRules.setIndeterminate(true); - dialogImportRulesIndeterminate = true; - } - }); - } - } - - @Override - public void onDNSCryptRuleLineAdded(int count) { - linesAdded = count; - dialogMessage = String.format(getString(R.string.import_dnscrypt_rules_dialog_message), linesAdded); - if (handler != null) { - handler.post(() -> { - if (tvDialogImportRules != null) { - tvDialogImportRules.setText(dialogMessage); - } - }); - } - } - - @Override - public void onDNSCryptRuleLinesAddingFinished() { - if (handler != null) { - handler.post(() -> { - if (tvDialogImportRules!= null && pbDialogImportRules != null && btnDialogImportRules != null) { - dialogMessage = String.format(getString(R.string.import_dnscrypt_rules_complete_dialog_message), linesAdded); - tvDialogImportRules.setText(dialogMessage); - pbDialogImportRules.setIndeterminate(false); - pbDialogImportRules.setVisibility(View.GONE); - dialogImportRulesIndeterminate = false; - buttonText = R.string.ok; - btnDialogImportRules.setText(buttonText); - } - }); - } - - } - - @Override - public void onDestroy() { - super.onDestroy(); - - if (importThread != null && importThread.isAlive()) { - importThread.interrupt(); - } - } -} diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dnscrypt_fragment/DNSCryptRunFragment.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dnscrypt_fragment/DNSCryptRunFragment.java index d23308350..fcda84132 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dnscrypt_fragment/DNSCryptRunFragment.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dnscrypt_fragment/DNSCryptRunFragment.java @@ -42,14 +42,20 @@ import android.widget.ScrollView; import android.widget.TextView; +import pan.alexander.tordnscrypt.App; import pan.alexander.tordnscrypt.MainActivity; import pan.alexander.tordnscrypt.R; import pan.alexander.tordnscrypt.TopFragment; +import pan.alexander.tordnscrypt.modules.ModulesStatus; import pan.alexander.tordnscrypt.utils.root.RootExecService; import static android.util.TypedValue.COMPLEX_UNIT_PX; import static pan.alexander.tordnscrypt.TopFragment.DNSCryptVersion; import static pan.alexander.tordnscrypt.TopFragment.TOP_BROADCAST; +import static pan.alexander.tordnscrypt.utils.enums.ModuleState.FAULT; +import static pan.alexander.tordnscrypt.utils.enums.ModuleState.RUNNING; +import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPED; +import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPING; import static pan.alexander.tordnscrypt.utils.logger.Logger.loge; import com.google.android.material.divider.MaterialDivider; @@ -207,10 +213,28 @@ public void onDestroyView() { @Override public void onClick(View v) { if (v.getId() == R.id.btnDNSCryptStart) { + letFirewallStop(); presenter.startButtonOnClick(); } } + private void letFirewallStop() { + ModulesStatus modulesStatus = ModulesStatus.getInstance(); + if (modulesStatus.getFirewallState() != STOPPED + && modulesStatus.getDnsCryptState() == RUNNING + && (modulesStatus.getTorState() == STOPPED + || modulesStatus.getTorState() == STOPPING + || modulesStatus.getTorState() == FAULT) + && (modulesStatus.getItpdState() == STOPPED + || modulesStatus.getItpdState() == STOPPING + || modulesStatus.getItpdState() == FAULT)) { + modulesStatus.setFirewallState( + STOPPING, + App.getInstance().getDaggerComponent().getPreferenceRepository().get() + ); + } + } + @Override public void setDNSCryptStatus(int resourceText, int resourceColor) { tvDNSStatus.setText(resourceText); diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/bridges/RequestBridgesInteractor.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/bridges/RequestBridgesInteractor.kt index e273508e1..f27c6d5ed 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/bridges/RequestBridgesInteractor.kt +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/bridges/RequestBridgesInteractor.kt @@ -119,10 +119,14 @@ class RequestBridgesInteractor @Inject constructor( lockWakeLock() - val query = linkedMapOf().apply { - put("captcha_challenge_field", secretCode) - put("captcha_response_field", captchaText) - put("submit", "submit") + val query = if (secretCode.isNotEmpty() && captchaText.isNotEmpty()) { + linkedMapOf().apply { + put("captcha_challenge_field", secretCode) + put("captcha_response_field", captchaText) + put("submit", "submit") + } + } else { + linkedMapOf() } val url = if (ipv6) { diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/connection_records/ConnectionRecordsConverter.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/connection_records/ConnectionRecordsConverter.kt index 29ace49cc..38fee8599 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/connection_records/ConnectionRecordsConverter.kt +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/connection_records/ConnectionRecordsConverter.kt @@ -506,7 +506,7 @@ class ConnectionRecordsConverter @Inject constructor( private fun updateVars() { - iptablesFirewall.get().prepareUidAllowed() + iptablesFirewall.get().prepareUidAllowed(NetworkChecker.isVpnActive(context)) blockIPv6 = isBlockIPv6() meteredNetwork = isMeteredNetwork() vpnDNS = VpnBuilder.vpnDnsSet diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/enums/DNSCryptRulesVariant.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/dns_rules/DnsRuleType.kt similarity index 82% rename from tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/enums/DNSCryptRulesVariant.kt rename to tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/dns_rules/DnsRuleType.kt index 517f38e09..6506011b7 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/enums/DNSCryptRulesVariant.kt +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/dns_rules/DnsRuleType.kt @@ -17,13 +17,12 @@ Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com */ -package pan.alexander.tordnscrypt.utils.enums +package pan.alexander.tordnscrypt.domain.dns_rules -enum class DNSCryptRulesVariant { - BLACKLIST_HOSTS, - BLACKLIST_IPS, - WHITELIST_HOSTS, +enum class DnsRuleType { + BLACKLIST, + WHITELIST, + IP_BLACKLIST, FORWARDING, - CLOAKING, - UNDEFINED -} \ No newline at end of file + CLOAKING +} diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/dns_rules/DnsRulesInteractor.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/dns_rules/DnsRulesInteractor.kt new file mode 100644 index 000000000..166d64421 --- /dev/null +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/dns_rules/DnsRulesInteractor.kt @@ -0,0 +1,35 @@ +/* + This file is part of InviZible Pro. + + InviZible Pro is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + InviZible Pro is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with InviZible Pro. If not, see . + + Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com + */ + +package pan.alexander.tordnscrypt.domain.dns_rules + +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.recycler.DnsRuleRecycleItem + +interface DnsRulesInteractor { + + suspend fun getMixedRulesMetadata(type: DnsRuleType): DnsRulesMetadata.MixedDnsRulesMetadata + suspend fun getSingleRules(type: DnsRuleType): List + suspend fun saveSingleRules(type: DnsRuleType, rules: List) + suspend fun getRemoteRulesMetadata(type: DnsRuleType): DnsRulesMetadata.RemoteDnsRulesMetadata + suspend fun getLocalRulesMetadata(type: DnsRuleType): DnsRulesMetadata.LocalDnsRulesMetadata + suspend fun clearRemoteRules(type: DnsRuleType) + suspend fun clearLocalRules(type: DnsRuleType) + + suspend fun isExternalStorageAllowsDirectAccess(): Boolean +} diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/dns_rules/DnsRulesInteractorImpl.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/dns_rules/DnsRulesInteractorImpl.kt new file mode 100644 index 000000000..a8e710209 --- /dev/null +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/dns_rules/DnsRulesInteractorImpl.kt @@ -0,0 +1,120 @@ +/* + This file is part of InviZible Pro. + + InviZible Pro is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + InviZible Pro is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with InviZible Pro. If not, see . + + Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com + */ + +package pan.alexander.tordnscrypt.domain.dns_rules + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.runInterruptible +import kotlinx.coroutines.withContext +import pan.alexander.tordnscrypt.di.CoroutinesModule +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.recycler.DnsRuleRecycleItem +import pan.alexander.tordnscrypt.utils.Utils.isLogsDirAccessible +import javax.inject.Inject +import javax.inject.Named + +class DnsRulesInteractorImpl @Inject constructor( + private val repository: DnsRulesRepository, + @Named(CoroutinesModule.DISPATCHER_IO) + private val dispatcherIo: CoroutineDispatcher +) : DnsRulesInteractor { + + override suspend fun getMixedRulesMetadata(type: DnsRuleType): DnsRulesMetadata.MixedDnsRulesMetadata = + withContext(dispatcherIo) { + when (type) { + DnsRuleType.BLACKLIST -> repository.getMixedBlacklistRulesMetadata() + DnsRuleType.WHITELIST -> repository.getMixedWhitelistRulesMetadata() + DnsRuleType.IP_BLACKLIST -> repository.getMixedIpBlacklistRulesMetadata() + DnsRuleType.FORWARDING -> repository.getMixedForwardingRulesMetadata() + DnsRuleType.CLOAKING -> repository.getMixedCloakingRulesMetadata() + } + } + + override suspend fun getSingleRules(type: DnsRuleType): List = + withContext(dispatcherIo) { + when (type) { + DnsRuleType.BLACKLIST -> repository.getSingleBlacklistRules() + DnsRuleType.WHITELIST -> repository.getSingleWhitelistRules() + DnsRuleType.IP_BLACKLIST -> repository.getSingleIpBlacklistRules() + DnsRuleType.FORWARDING -> repository.getSingleForwardingRules() + DnsRuleType.CLOAKING -> repository.getSingleCloakingRules() + } + } + + override suspend fun saveSingleRules( + type: DnsRuleType, + rules: List + ) = withContext(dispatcherIo) { + when (type) { + DnsRuleType.BLACKLIST -> repository.saveSingleBlacklistRules(rules) + DnsRuleType.WHITELIST -> repository.saveSingleWhitelistRules(rules) + DnsRuleType.IP_BLACKLIST -> repository.saveSingleIpBlacklistRules(rules) + DnsRuleType.FORWARDING -> repository.saveSingleForwardingRules(rules) + DnsRuleType.CLOAKING -> repository.saveSingleCloakingRules(rules) + } + } + + override suspend fun getRemoteRulesMetadata( + type: DnsRuleType + ): DnsRulesMetadata.RemoteDnsRulesMetadata = withContext(dispatcherIo) { + when (type) { + DnsRuleType.BLACKLIST -> repository.getRemoteBlacklistRulesMetadata() + DnsRuleType.WHITELIST -> repository.getRemoteWhitelistRulesMetadata() + DnsRuleType.IP_BLACKLIST -> repository.getRemoteIpBlacklistRulesMetadata() + DnsRuleType.FORWARDING -> repository.getRemoteForwardingRulesMetadata() + DnsRuleType.CLOAKING -> repository.getRemoteCloakingRulesMetadata() + } + } + + override suspend fun getLocalRulesMetadata( + type: DnsRuleType + ): DnsRulesMetadata.LocalDnsRulesMetadata = withContext(dispatcherIo) { + when (type) { + DnsRuleType.BLACKLIST -> repository.getLocalBlacklistRulesMetadata() + DnsRuleType.WHITELIST -> repository.getLocalWhitelistRulesMetadata() + DnsRuleType.IP_BLACKLIST -> repository.getLocalIpBlacklistRulesMetadata() + DnsRuleType.FORWARDING -> repository.getLocalForwardingRulesMetadata() + DnsRuleType.CLOAKING -> repository.getLocalCloakingRulesMetadata() + } + } + + override suspend fun clearRemoteRules(type: DnsRuleType) = withContext(dispatcherIo) { + when (type) { + DnsRuleType.BLACKLIST -> repository.clearRemoteBlacklistRules() + DnsRuleType.WHITELIST -> repository.clearRemoteWhitelistRules() + DnsRuleType.IP_BLACKLIST -> repository.clearRemoteIpBlacklistRules() + DnsRuleType.FORWARDING -> repository.clearRemoteForwardingRules() + DnsRuleType.CLOAKING -> repository.clearRemoteCloakingRules() + } + } + + override suspend fun clearLocalRules(type: DnsRuleType) = withContext(dispatcherIo) { + when (type) { + DnsRuleType.BLACKLIST -> repository.clearLocalBlacklistRules() + DnsRuleType.WHITELIST -> repository.clearLocalWhitelistRules() + DnsRuleType.IP_BLACKLIST -> repository.clearLocalIpBlacklistRules() + DnsRuleType.FORWARDING -> repository.clearLocalForwardingRules() + DnsRuleType.CLOAKING -> repository.clearLocalCloakingRules() + } + } + + override suspend fun isExternalStorageAllowsDirectAccess(): Boolean = + runInterruptible(dispatcherIo) { + isLogsDirAccessible() + } +} diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/show_rules/Rules.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/dns_rules/DnsRulesMetadata.kt similarity index 59% rename from tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/show_rules/Rules.java rename to tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/dns_rules/DnsRulesMetadata.kt index 28ecb76cb..cff217bf3 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/show_rules/Rules.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/dns_rules/DnsRulesMetadata.kt @@ -17,20 +17,29 @@ Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com */ -package pan.alexander.tordnscrypt.settings.show_rules; +package pan.alexander.tordnscrypt.domain.dns_rules -class Rules { +import java.util.Date - String text; - boolean active; - boolean locked; - boolean subscription; +sealed class DnsRulesMetadata { + data class RemoteDnsRulesMetadata( + val name: String, + val url: String, + val date: Date, + val count: Int, + val size: Long + ) : DnsRulesMetadata() + + data class LocalDnsRulesMetadata( + val name: String, + val date: Date, + val count: Int, + val size: Long + ) : DnsRulesMetadata() + + data class MixedDnsRulesMetadata( + val count: Int + ) - Rules(String text, boolean active, boolean locked, boolean subscription) { - this.text = text; - this.active = active; - this.locked = locked; - this.subscription = subscription; - } } diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/dns_rules/DnsRulesRepository.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/dns_rules/DnsRulesRepository.kt new file mode 100644 index 000000000..b4396add5 --- /dev/null +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/dns_rules/DnsRulesRepository.kt @@ -0,0 +1,70 @@ +/* + This file is part of InviZible Pro. + + InviZible Pro is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + InviZible Pro is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with InviZible Pro. If not, see . + + Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com + */ + +package pan.alexander.tordnscrypt.domain.dns_rules + +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.recycler.DnsRuleRecycleItem + +interface DnsRulesRepository { + + suspend fun getMixedBlacklistRulesMetadata(): DnsRulesMetadata.MixedDnsRulesMetadata + suspend fun getSingleBlacklistRules(): List + fun saveSingleBlacklistRules(rules: List) + suspend fun getRemoteBlacklistRulesMetadata(): DnsRulesMetadata.RemoteDnsRulesMetadata + fun clearRemoteBlacklistRules() + suspend fun getLocalBlacklistRulesMetadata(): DnsRulesMetadata.LocalDnsRulesMetadata + fun clearLocalBlacklistRules() + + suspend fun getMixedWhitelistRulesMetadata(): DnsRulesMetadata.MixedDnsRulesMetadata + suspend fun getSingleWhitelistRules(): List + fun saveSingleWhitelistRules(rules: List) + suspend fun getRemoteWhitelistRulesMetadata(): DnsRulesMetadata.RemoteDnsRulesMetadata + fun clearRemoteWhitelistRules() + suspend fun getLocalWhitelistRulesMetadata(): DnsRulesMetadata.LocalDnsRulesMetadata + fun clearLocalWhitelistRules() + + suspend fun getMixedIpBlacklistRulesMetadata(): DnsRulesMetadata.MixedDnsRulesMetadata + suspend fun getSingleIpBlacklistRules(): List + fun saveSingleIpBlacklistRules(rules: List) + suspend fun getRemoteIpBlacklistRulesMetadata(): DnsRulesMetadata.RemoteDnsRulesMetadata + fun clearRemoteIpBlacklistRules() + suspend fun getLocalIpBlacklistRulesMetadata(): DnsRulesMetadata.LocalDnsRulesMetadata + fun clearLocalIpBlacklistRules() + + suspend fun getMixedForwardingRulesMetadata(): DnsRulesMetadata.MixedDnsRulesMetadata + suspend fun getSingleForwardingRules(): List + fun saveSingleForwardingRules(rules: List) + suspend fun getRemoteForwardingRulesMetadata(): DnsRulesMetadata.RemoteDnsRulesMetadata + fun clearRemoteForwardingRules() + suspend fun getLocalForwardingRulesMetadata(): DnsRulesMetadata.LocalDnsRulesMetadata + fun clearLocalForwardingRules() + + suspend fun getMixedCloakingRulesMetadata(): DnsRulesMetadata.MixedDnsRulesMetadata + suspend fun getSingleCloakingRules(): List + fun saveSingleCloakingRules(rules: List) + suspend fun getRemoteCloakingRulesMetadata(): DnsRulesMetadata.RemoteDnsRulesMetadata + fun clearRemoteCloakingRules() + suspend fun getLocalCloakingRulesMetadata(): DnsRulesMetadata.LocalDnsRulesMetadata + fun clearLocalCloakingRules() + + companion object { + const val LOCAL_RULES_DEFAULT_HEADER = "local-rules.txt" + const val REMOTE_RULES_DEFAULT_HEADER = "remote-rules.txt" + } +} diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/iptables/IptablesFirewall.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/iptables/IptablesFirewall.kt index bc6729761..5d462e7ae 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/iptables/IptablesFirewall.kt +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/iptables/IptablesFirewall.kt @@ -20,6 +20,7 @@ package pan.alexander.tordnscrypt.iptables import android.content.Context +import android.os.Build import pan.alexander.tordnscrypt.domain.preferences.PreferenceRepository import pan.alexander.tordnscrypt.iptables.IptablesConstants.* import pan.alexander.tordnscrypt.modules.ModulesStatus @@ -35,12 +36,14 @@ import pan.alexander.tordnscrypt.settings.tor_apps.ApplicationData.Companion.SPE import pan.alexander.tordnscrypt.utils.Constants.* import pan.alexander.tordnscrypt.utils.Utils import pan.alexander.tordnscrypt.utils.apps.InstalledApplicationsManager +import pan.alexander.tordnscrypt.utils.connectionchecker.NetworkChecker import pan.alexander.tordnscrypt.utils.connectionchecker.NetworkChecker.isCellularActive import pan.alexander.tordnscrypt.utils.connectionchecker.NetworkChecker.isEthernetActive import pan.alexander.tordnscrypt.utils.connectionchecker.NetworkChecker.isRoaming import pan.alexander.tordnscrypt.utils.connectionchecker.NetworkChecker.isVpnActive import pan.alexander.tordnscrypt.utils.connectionchecker.NetworkChecker.isWifiActive import pan.alexander.tordnscrypt.utils.connectivitycheck.ConnectivityCheckManager +import pan.alexander.tordnscrypt.utils.enums.ModuleState import pan.alexander.tordnscrypt.utils.enums.OperationMode import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.* import pan.alexander.tordnscrypt.vpn.VpnUtils @@ -66,10 +69,34 @@ class IptablesFirewall @Inject constructor( val uidAllowed by lazy { hashSetOf() } val uidSpecialAllowed by lazy { hashSetOf() } val uidLanAllowed by lazy { hashSetOf() } + private val uidUnderlyingVpnAllowed by lazy { hashSetOf() } fun getFirewallRules(tetheringActive: Boolean): List { - prepareUidAllowed() + val vpnActive = isVpnActive(context) + + prepareUidAllowed(vpnActive) + + if (modulesStatus.mode == OperationMode.ROOT_MODE) { + modulesStatus.setFirewallState(ModuleState.RUNNING, preferences) + } + + var activeInterface = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + NetworkChecker.getCurrentActiveInterface(context) + } else { + "" + } + val underlyingVpnInterface = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + && vpnActive && activeInterface.isNotEmpty() + ) { + NetworkChecker.getUnderlyingVpnActiveInterface(context).also { + if (it.isEmpty()) { + activeInterface = "" + } + } + } else { + "" + } val iptables = getIptables() @@ -84,8 +111,14 @@ class IptablesFirewall @Inject constructor( ).plus( getLanRules(iptables) ).plus( - getAppRulesByConnectionType(iptables) - ).plus( + getAppRulesByConnectionType(iptables, activeInterface) + ).let { + if (underlyingVpnInterface.isNotEmpty() && underlyingVpnInterface != activeInterface) { + it.plus(getVpnUnderlyingAppRulesByConnectionType(iptables, underlyingVpnInterface)) + } else { + it + } + }.plus( getSpecialRules(iptables) ).plus( "$iptables -A $FILTER_OUTPUT_FIREWALL -j REJECT" @@ -104,6 +137,10 @@ class IptablesFirewall @Inject constructor( fun getClearFirewallRules(): List { + if (modulesStatus.mode == OperationMode.ROOT_MODE) { + modulesStatus.setFirewallState(ModuleState.STOPPED, preferences) + } + val iptables = getIptables() return arrayListOf( @@ -122,7 +159,6 @@ class IptablesFirewall @Inject constructor( ).plus( VpnUtils.nonTorList .asSequence() - .filter { it != "127.0.0.0/8" } //exclude localhost .filter { it != META_ADDRESS } //exclude meta address .map { "$iptables -A $FILTER_OUTPUT_FIREWALL -d $it -j $FILTER_FIREWALL_LAN" @@ -146,23 +182,61 @@ class IptablesFirewall @Inject constructor( range.first() == SPECIAL_UID_KERNEL -> "$iptables -A $FILTER_FIREWALL_LAN -m owner ! --uid-owner 0:999999999 -j MARK --set-mark $FIREWALL_RETURN_MARK 2> /dev/null || true" else -> "" } + range.size > 1 -> when { range.first() >= 0 -> "$iptables -A $FILTER_FIREWALL_LAN -m owner --uid-owner ${range.first()}:${range.last()} -j MARK --set-mark $FIREWALL_RETURN_MARK 2> /dev/null || true" else -> "" } + else -> "" } } } - private fun getAppRulesByConnectionType(iptables: String): List = with(IptablesUtils) { - uidAllowed.groupToRanges().map { + private fun getAppRulesByConnectionType( + iptables: String, + activeInterface: String + ): List = with(IptablesUtils) { + if (activeInterface.isNotEmpty()) { + uidAllowed.groupToRanges().map { + when { + it.size == 1 -> + "$iptables -A $FILTER_OUTPUT_FIREWALL -m owner --uid-owner ${it.first()} -o $activeInterface -j RETURN" + + it.size > 1 -> + "$iptables -A $FILTER_OUTPUT_FIREWALL -m owner --uid-owner ${it.first()}:${it.last()} -o $activeInterface -j RETURN" + + else -> "" + } + } + } else { + uidAllowed.groupToRanges().map { + when { + it.size == 1 -> + "$iptables -A $FILTER_OUTPUT_FIREWALL -m owner --uid-owner ${it.first()} -j RETURN" + + it.size > 1 -> + "$iptables -A $FILTER_OUTPUT_FIREWALL -m owner --uid-owner ${it.first()}:${it.last()} -j RETURN" + + else -> "" + } + } + } + } + + private fun getVpnUnderlyingAppRulesByConnectionType( + iptables: String, + underlyingInterface: String + ): List = with(IptablesUtils) { + uidUnderlyingVpnAllowed.groupToRanges().map { when { it.size == 1 -> - "$iptables -A $FILTER_OUTPUT_FIREWALL -m owner --uid-owner ${it.first()} -j RETURN" + "$iptables -A $FILTER_OUTPUT_FIREWALL -m owner --uid-owner ${it.first()} -o $underlyingInterface -j RETURN" + it.size > 1 -> - "$iptables -A $FILTER_OUTPUT_FIREWALL -m owner --uid-owner ${it.first()}:${it.last()} -j RETURN" + "$iptables -A $FILTER_OUTPUT_FIREWALL -m owner --uid-owner ${it.first()}:${it.last()} -o $underlyingInterface -j RETURN" + else -> "" } } @@ -181,12 +255,14 @@ class IptablesFirewall @Inject constructor( "$iptables -A $FILTER_OUTPUT_FIREWALL -m owner ! --uid-owner 0:999999999 -j RETURN" ) } + SPECIAL_UID_NTP -> { arrayListOf( "$iptables -t mangle -A $MANGLE_FIREWALL_ALLOW -p udp --sport $SPECIAL_PORT_NTP -m owner --uid-owner 1000 -j CONNMARK --set-mark $FIREWALL_RETURN_MARK || true", "$iptables -t mangle -A $MANGLE_FIREWALL_ALLOW -p udp --dport $SPECIAL_PORT_NTP -m owner --uid-owner 1000 -j CONNMARK --set-mark $FIREWALL_RETURN_MARK || true", ) } + SPECIAL_UID_AGPS -> { arrayListOf( "$iptables -t mangle -A $MANGLE_FIREWALL_ALLOW -p tcp --dport $SPECIAL_PORT_AGPS1 -j CONNMARK --set-mark $FIREWALL_RETURN_MARK || true", @@ -195,11 +271,13 @@ class IptablesFirewall @Inject constructor( "$iptables -t mangle -A $MANGLE_FIREWALL_ALLOW -p udp --dport $SPECIAL_PORT_AGPS2 -j CONNMARK --set-mark $FIREWALL_RETURN_MARK || true" ) } + SPECIAL_UID_CONNECTIVITY_CHECK -> { connectivityCheckManager.get().getConnectivityCheckIps().map { ip -> "$iptables -t mangle -A $MANGLE_FIREWALL_ALLOW -d $ip -j CONNMARK --set-mark $FIREWALL_RETURN_MARK || true" } } + else -> emptyList() } } @@ -222,10 +300,13 @@ class IptablesFirewall @Inject constructor( emptyList() } - fun prepareUidAllowed() { + fun prepareUidAllowed(vpnActive: Boolean) { clearAllowedUids() - fillAllowedAndSpecialUids(getUidsAllowed()) + fillAllowedAndSpecialUids(getUidsAllowed(vpnActive = vpnActive, skipVpn = false)) fillLanAllowed() + if (vpnActive) { + fillAllowedUnderlyingVpnUid(getUidsAllowed(vpnActive = true, skipVpn = true)) + } } private fun fillAllowedAndSpecialUids(listAllowed: List) = @@ -237,6 +318,13 @@ class IptablesFirewall @Inject constructor( } } + private fun fillAllowedUnderlyingVpnUid(listAllowed: List) = + listAllowed.forEach { + if (it?.matches(positiveNumberRegex) == true && it.toLong() <= Int.MAX_VALUE) { + uidUnderlyingVpnAllowed.add(it.toInt()) + } + } + private fun fillLanAllowed() { preferences.getStringSetPreference(APPS_ALLOW_LAN_PREF).forEach { if (it.matches(numberRegex) && it.toLong() <= Int.MAX_VALUE) { @@ -249,9 +337,10 @@ class IptablesFirewall @Inject constructor( uidAllowed.clear() uidSpecialAllowed.clear() uidLanAllowed.clear() + uidUnderlyingVpnAllowed.clear() } - private fun getUidsAllowed(): List { + private fun getUidsAllowed(vpnActive: Boolean, skipVpn: Boolean): List { val firewallEnabled = preferences.getBoolPreference(FIREWALL_ENABLED) if (!firewallEnabled || modulesStatus.mode != OperationMode.ROOT_MODE) { @@ -266,18 +355,24 @@ class IptablesFirewall @Inject constructor( val listAllowed = arrayListOf() when { - isVpnActive(context) && !ttlFix -> + vpnActive && !ttlFix && !skipVpn -> listAllowed.addAll(preferences.getStringSetPreference(APPS_ALLOW_VPN)) + isWifiActive(context) || isEthernetActive(context) -> listAllowed.addAll(preferences.getStringSetPreference(APPS_ALLOW_WIFI_PREF)) + isRoaming(context) -> listAllowed.addAll(preferences.getStringSetPreference(APPS_ALLOW_ROAMING)) + isCellularActive(context) -> listAllowed.addAll(preferences.getStringSetPreference(APPS_ALLOW_GSM_PREF)) + isWifiActive(context, true) -> listAllowed.addAll(preferences.getStringSetPreference(APPS_ALLOW_WIFI_PREF)) + isCellularActive(context, true) -> listAllowed.addAll(preferences.getStringSetPreference(APPS_ALLOW_GSM_PREF)) + else -> listAllowed.apply { add(SPECIAL_UID_KERNEL.toString()) add(ROOT_DEFAULT_UID.toString()) diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/iptables/IptablesRules.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/iptables/IptablesRules.java index 04625fc8e..510d7213c 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/iptables/IptablesRules.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/iptables/IptablesRules.java @@ -24,7 +24,12 @@ import pan.alexander.tordnscrypt.utils.enums.ModuleState; public interface IptablesRules { - List configureIptables(ModuleState dnsCryptState, ModuleState torState, ModuleState itpdState); + List configureIptables( + ModuleState dnsCryptState, + ModuleState torState, + ModuleState itpdState, + ModuleState firewallState + ); List fastUpdate(); void refreshFixTTLRules(); List clearAll(); diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/iptables/ModulesIptablesRules.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/iptables/ModulesIptablesRules.java index 644eb0e7e..d4da7bc2a 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/iptables/ModulesIptablesRules.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/iptables/ModulesIptablesRules.java @@ -66,6 +66,7 @@ import static pan.alexander.tordnscrypt.utils.Constants.NFLOG_GROUP; import static pan.alexander.tordnscrypt.utils.Constants.NFLOG_PREFIX; import static pan.alexander.tordnscrypt.utils.Constants.QUAD_DNS_41; +import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STARTING; import static pan.alexander.tordnscrypt.utils.logger.Logger.logi; import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.ALL_THROUGH_TOR; import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.APPS_DIRECT_UDP; @@ -126,7 +127,12 @@ public ModulesIptablesRules(Context context) { } @Override - public List configureIptables(ModuleState dnsCryptState, ModuleState torState, ModuleState itpdState) { + public List configureIptables( + ModuleState dnsCryptState, + ModuleState torState, + ModuleState itpdState, + ModuleState firewallState + ) { iptables = pathVars.getIptablesPath(); ip6tables = pathVars.getIp6tablesPath(); @@ -377,12 +383,18 @@ public List configureIptables(ModuleState dnsCryptState, ModuleState tor commands = getBlockingRules(appUID, blockHOTSPOT, unblockHOTSPOT); - } else if (killSwitch && dnsCryptState != RUNNING && torState != RUNNING && itpdState != RUNNING) { + } else if (killSwitch + && dnsCryptState != RUNNING && torState != RUNNING && itpdState != RUNNING + && firewallState != RUNNING && firewallState != STARTING) { showKillSwitchNotification(); commands = getBlockingRules(appUID, blockHOTSPOT, unblockHOTSPOT); + if (modulesStatus.getMode() == ROOT_MODE) { + modulesStatus.setFirewallState(ModuleState.STOPPED, preferences); + } + } else if (dnsCryptState == RUNNING && torState == RUNNING) { cancelKillSwitchNotificationIfNeeded(); @@ -598,6 +610,56 @@ public List configureIptables(ModuleState dnsCryptState, ModuleState tor blockHOTSPOT )); + List commandsTether = tethering.activateTethering(false); + if (!commandsTether.isEmpty()) { + commands.addAll(commandsTether); + } + if (firewallEnabled) { + commands.addAll(firewall.getFirewallRules(tethering.isTetheringActive())); + } else { + commands.addAll(firewall.getClearFirewallRules()); + } + commands.add(iptables + "-D OUTPUT -j " + FILTER_OUTPUT_BLOCKING + " 2> /dev/null || true"); + } else if (dnsCryptState == STOPPED && torState == STOPPED + && (firewallState == STARTING || firewallState == RUNNING)) { + + cancelKillSwitchNotificationIfNeeded(); + + commands = new ArrayList<>(Arrays.asList( + iptables + "-F " + FILTER_OUTPUT_BLOCKING + " 2> /dev/null", + iptables + "-D OUTPUT -j " + FILTER_OUTPUT_BLOCKING + " 2> /dev/null || true", + iptables + "-N " + FILTER_OUTPUT_BLOCKING + " 2> /dev/null", + iptables + "-A " + FILTER_OUTPUT_BLOCKING + " -m state --state ESTABLISHED,RELATED -j RETURN", + iptables + "-A " + FILTER_OUTPUT_BLOCKING + " -m owner --uid-owner " + appUID + " -j RETURN", + criticalUidsAllowed, + iptables + "-A " + FILTER_OUTPUT_BLOCKING + " -j DROP", + iptables + "-I OUTPUT -j " + FILTER_OUTPUT_BLOCKING, + ip6tables + "-D OUTPUT -j DROP 2> /dev/null || true", + ip6tables + "-D OUTPUT -m owner --uid-owner " + appUID + " -j ACCEPT 2> /dev/null || true", + ip6tables + "-I OUTPUT -j DROP", + ip6tables + "-I OUTPUT -m owner --uid-owner " + appUID + " -j ACCEPT", + iptables + "-t nat -F " + NAT_OUTPUT_CORE + " 2> /dev/null", + iptables + "-t nat -D OUTPUT -j " + NAT_OUTPUT_CORE + " 2> /dev/null || true", + iptables + "-F " + FILTER_OUTPUT_CORE + " 2> /dev/null", + iptables + "-D OUTPUT -j " + FILTER_OUTPUT_CORE + " 2> /dev/null || true", + busybox + "sleep 1 || true", + iptables + "-t nat -N " + NAT_OUTPUT_CORE + " 2> /dev/null", + iptables + "-t nat -I OUTPUT -j " + NAT_OUTPUT_CORE, + iptables + "-t nat -A " + NAT_OUTPUT_CORE + " -p all -d 127.0.0.1/32 -j RETURN", + blockHttpRuleNatTCP, + blockHttpRuleNatUDP, + iptables + "-N " + FILTER_OUTPUT_CORE + " 2> /dev/null", + nflogDns, + iptables + "-A " + FILTER_OUTPUT_CORE + " -p udp -m udp --dport 53 -j ACCEPT", + iptables + "-A " + FILTER_OUTPUT_CORE + " -p tcp -m tcp --dport 53 -j ACCEPT", + blockRejectAddressFilter, + iptables + "-A " + FILTER_OUTPUT_CORE + " -m state --state ESTABLISHED,RELATED -j RETURN", + iptables + "-I OUTPUT -j " + FILTER_OUTPUT_CORE, + nflogPackets, + unblockHOTSPOT, + blockHOTSPOT + )); + List commandsTether = tethering.activateTethering(false); if (!commandsTether.isEmpty()) { commands.addAll(commandsTether); diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/itpd_fragment/ITPDRunFragment.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/itpd_fragment/ITPDRunFragment.java index 52030e767..732576781 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/itpd_fragment/ITPDRunFragment.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/itpd_fragment/ITPDRunFragment.java @@ -27,6 +27,7 @@ import android.graphics.Rect; import android.os.Bundle; +import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.localbroadcastmanager.content.LocalBroadcastManager; @@ -44,14 +45,23 @@ import android.widget.ScrollView; import android.widget.TextView; +import pan.alexander.tordnscrypt.App; import pan.alexander.tordnscrypt.MainActivity; import pan.alexander.tordnscrypt.R; import pan.alexander.tordnscrypt.TopFragment; +import pan.alexander.tordnscrypt.domain.preferences.PreferenceRepository; +import pan.alexander.tordnscrypt.modules.ModulesAux; +import pan.alexander.tordnscrypt.modules.ModulesStatus; import pan.alexander.tordnscrypt.utils.root.RootExecService; import static android.util.TypedValue.COMPLEX_UNIT_PX; import static pan.alexander.tordnscrypt.TopFragment.ITPDVersion; import static pan.alexander.tordnscrypt.TopFragment.TOP_BROADCAST; +import static pan.alexander.tordnscrypt.utils.enums.ModuleState.FAULT; +import static pan.alexander.tordnscrypt.utils.enums.ModuleState.RUNNING; +import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STARTING; +import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPED; +import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPING; import static pan.alexander.tordnscrypt.utils.logger.Logger.loge; import com.google.android.material.divider.MaterialDivider; @@ -76,7 +86,7 @@ public ITPDRunFragment() { @SuppressLint("SetTextI18n") @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view; @@ -213,10 +223,32 @@ public void onDestroyView() { @Override public void onClick(View v) { if (v.getId() == R.id.btnITPDStart) { + manageFirewall(); presenter.startButtonOnClick(); } } + private void manageFirewall() { + ModulesStatus modulesStatus = ModulesStatus.getInstance(); + if ((modulesStatus.getDnsCryptState() == STOPPED + || modulesStatus.getDnsCryptState() == STOPPING + || modulesStatus.getDnsCryptState() == FAULT) + && (modulesStatus.getTorState() == STOPPED + || modulesStatus.getTorState() == STOPPING + || modulesStatus.getTorState() == FAULT)) { + PreferenceRepository preferenceRepository = App.getInstance() + .getDaggerComponent().getPreferenceRepository().get(); + if (modulesStatus.getFirewallState() != STOPPED + && modulesStatus.getItpdState() == RUNNING) { + modulesStatus.setFirewallState(STOPPING, preferenceRepository); + } else if (modulesStatus.getFirewallState() != RUNNING + && modulesStatus.getItpdState() == STOPPED) { + modulesStatus.setFirewallState(STARTING, preferenceRepository); + ModulesAux.makeModulesStateExtraLoop(requireContext()); + } + } + } + @Override public void setITPDStatus(int resourceText, int resourceColor) { tvITPDStatus.setText(resourceText); diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/main_fragment/MainFragment.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/main_fragment/MainFragment.java index 5c9cea6f9..459097787 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/main_fragment/MainFragment.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/main_fragment/MainFragment.java @@ -67,12 +67,15 @@ import pan.alexander.tordnscrypt.dnscrypt_fragment.DNSCryptFragmentPresenter; import pan.alexander.tordnscrypt.dnscrypt_fragment.DNSCryptFragmentReceiver; import pan.alexander.tordnscrypt.dnscrypt_fragment.DNSCryptFragmentView; +import pan.alexander.tordnscrypt.modules.ModulesAux; import pan.alexander.tordnscrypt.modules.ModulesStatus; import pan.alexander.tordnscrypt.tor_fragment.TorFragmentPresenter; import pan.alexander.tordnscrypt.tor_fragment.TorFragmentReceiver; import pan.alexander.tordnscrypt.tor_fragment.TorFragmentView; import pan.alexander.tordnscrypt.utils.Utils; +import pan.alexander.tordnscrypt.utils.enums.OperationMode; import pan.alexander.tordnscrypt.utils.root.RootExecService; +import pan.alexander.tordnscrypt.vpn.service.ServiceVPNHelper; import static android.util.TypedValue.COMPLEX_UNIT_PX; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; @@ -81,10 +84,14 @@ import static pan.alexander.tordnscrypt.TopFragment.ITPDVersion; import static pan.alexander.tordnscrypt.TopFragment.TOP_BROADCAST; import static pan.alexander.tordnscrypt.TopFragment.TorVersion; +import static pan.alexander.tordnscrypt.utils.enums.ModuleState.RUNNING; +import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STARTING; import static pan.alexander.tordnscrypt.utils.logger.Logger.loge; import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPED; import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPING; import static pan.alexander.tordnscrypt.utils.enums.ModuleState.UNDEFINED; +import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.FIREWALL_ENABLED; +import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.FIREWALL_WAS_STARTED; import com.google.android.material.divider.MaterialDivider; @@ -336,22 +343,50 @@ public void onClick(View v) { if (v.getId() == R.id.btnStartMainFragment) { + boolean runDnsCrypt = chbProtectDnsMainFragment.isChecked(); + boolean runTor = chbHideIpMainFragment.isChecked(); + boolean runITPD = chbAccessITPMainFragment.isChecked(); + if (modulesStatus.getDnsCryptState() == STOPPED && modulesStatus.getTorState() == STOPPED - && modulesStatus.getItpdState() == STOPPED) { + && modulesStatus.getItpdState() == STOPPED + && modulesStatus.getFirewallState() != RUNNING) { + + if (modulesStatus.getFirewallState() == STOPPED + && (modulesStatus.getMode() == OperationMode.VPN_MODE + || modulesStatus.getMode() == OperationMode.ROOT_MODE) + && isChildLockDisabled()) { + modulesStatus.setFirewallState(STARTING, preferenceRepository.get()); + if (!runDnsCrypt && !runTor && !runITPD && isFirewallEnabled()) { + ModulesAux.makeModulesStateExtraLoop(context); + ServiceVPNHelper.prepareVPNServiceIfRequired(getFragmentActivity(), modulesStatus); + } + } else if (isChildLockDisabled()) { + modulesStatus.setFirewallState(STOPPED, preferenceRepository.get()); + } - if (chbProtectDnsMainFragment.isChecked()) { + if (runDnsCrypt) { dnsCryptFragmentPresenter.startButtonOnClick(); } - if (chbHideIpMainFragment.isChecked()) { + if (runTor) { torFragmentPresenter.startButtonOnClick(); } - if (chbAccessITPMainFragment.isChecked()) { + if (runITPD) { itpdFragmentPresenter.startButtonOnClick(); } } else { + + if (modulesStatus.getFirewallState() != STOPPED && isChildLockDisabled()) { + modulesStatus.setFirewallState(STOPPING, preferenceRepository.get()); + if (!runDnsCrypt && !runTor && !runITPD && isFirewallEnabled()) { + ModulesAux.makeModulesStateExtraLoop(context); + } + } else if (isChildLockDisabled()) { + modulesStatus.setFirewallState(STOPPED, preferenceRepository.get()); + } + if (modulesStatus.getDnsCryptState() != STOPPED) { dnsCryptFragmentPresenter.startButtonOnClick(); } @@ -363,10 +398,25 @@ public void onClick(View v) { if (modulesStatus.getItpdState() != STOPPED) { itpdFragmentPresenter.startButtonOnClick(); } + } } } + private boolean isFirewallEnabled() { + return preferenceRepository.get().getBoolPreference(FIREWALL_ENABLED) + && preferenceRepository.get().getBoolPreference(FIREWALL_WAS_STARTED); + } + + private boolean isChildLockDisabled() { + boolean childLock = false; + Activity activity = getFragmentActivity(); + if (activity instanceof MainActivity) { + childLock = ((MainActivity) activity).childLockActive; + } + return !childLock; + } + @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { @@ -389,7 +439,8 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (modulesStatus.getDnsCryptState() != STOPPED || modulesStatus.getTorState() != STOPPED - || modulesStatus.getItpdState() != STOPPED) { + || modulesStatus.getItpdState() != STOPPED + || modulesStatus.getFirewallState() != STOPPED) { int id = buttonView.getId(); if (id == R.id.chbProtectDnsMainFragment) { @@ -425,10 +476,10 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { } } - private void refreshStartButtonText() { + public void refreshStartButtonText() { Context context = getActivity(); - if (context == null || orientationLandscape) { + if (context == null || btnStartMainFragment == null || orientationLandscape) { return; } @@ -436,7 +487,8 @@ private void refreshStartButtonText() { if ((modulesStatus.getDnsCryptState() == STOPPED || modulesStatus.getDnsCryptState() == UNDEFINED) && (modulesStatus.getTorState() == STOPPED || modulesStatus.getTorState() == UNDEFINED) - && (modulesStatus.getItpdState() == STOPPED || modulesStatus.getItpdState() == UNDEFINED)) { + && (modulesStatus.getItpdState() == STOPPED || modulesStatus.getItpdState() == UNDEFINED) + && (modulesStatus.getFirewallState() == STOPPED || modulesStatus.getFirewallState() == STOPPING)) { mainStartButtonDrawable = ResourcesCompat.getDrawable(context.getResources(), R.drawable.button_main_selector, context.getTheme()); @@ -466,6 +518,9 @@ private void refreshStartButtonText() { } private void setChbProtectDnsMainFragment(boolean checked) { + if (chbProtectDnsMainFragment == null) { + return; + } if (!chbProtectDnsMainFragment.isChecked() && checked) { chbProtectDnsMainFragment.setChecked(true); } else if (chbProtectDnsMainFragment.isChecked() && !checked) { @@ -602,6 +657,9 @@ public void scrollDNSCryptLogViewToBottom() { private void setChbHideIpMainFragment(boolean checked) { + if (chbHideIpMainFragment == null) { + return; + } if (!chbHideIpMainFragment.isChecked() && checked) { chbHideIpMainFragment.setChecked(true); } else if (chbHideIpMainFragment.isChecked() && !checked) { @@ -862,6 +920,9 @@ public void scrollITPDLogViewToBottom() { } private void setChbAccessITPMainFragment(boolean checked) { + if (chbAccessITPMainFragment == null) { + return; + } if (!chbAccessITPMainFragment.isChecked() && checked) { chbAccessITPMainFragment.setChecked(true); } else if (chbAccessITPMainFragment.isChecked() && !checked) { diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesAux.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesAux.java index 537cc84a8..7d495e2f5 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesAux.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesAux.java @@ -25,12 +25,15 @@ import pan.alexander.tordnscrypt.App; import pan.alexander.tordnscrypt.domain.preferences.PreferenceRepository; import pan.alexander.tordnscrypt.settings.PathVars; +import pan.alexander.tordnscrypt.utils.enums.ModuleState; import pan.alexander.tordnscrypt.utils.enums.OperationMode; import static pan.alexander.tordnscrypt.utils.enums.OperationMode.PROXY_MODE; import static pan.alexander.tordnscrypt.utils.enums.OperationMode.ROOT_MODE; import static pan.alexander.tordnscrypt.utils.enums.OperationMode.UNDEFINED; import static pan.alexander.tordnscrypt.utils.enums.OperationMode.VPN_MODE; +import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.FIREWALL_ENABLED; +import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.FIREWALL_WAS_STARTED; import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.OPERATION_MODE; public class ModulesAux { @@ -38,6 +41,7 @@ public class ModulesAux { private static final String DNSCRYPT_RUNNING_PREF = "DNSCrypt Running"; private static final String TOR_RUNNING_PREF = "Tor Running"; private static final String ITPD_RUNNING_PREF = "I2PD Running"; + private static final String FIREWALL_RUNNING_PREF = "Firewall Running"; public static void switchModes(boolean rootIsAvailable, boolean runModulesWithRoot, OperationMode operationMode) { ModulesStatus modulesStatus = ModulesStatus.getInstance(); @@ -105,6 +109,18 @@ public static void saveITPDStateRunning(boolean running) { //} } + public static boolean isFirewallSavedStateRunning() { + PreferenceRepository preferences = App.getInstance().getDaggerComponent().getPreferenceRepository().get(); + return preferences.getBoolPreference(FIREWALL_RUNNING_PREF) + && preferences.getBoolPreference(FIREWALL_ENABLED) + && preferences.getBoolPreference(FIREWALL_WAS_STARTED); + } + + public static void saveFirewallStateRunning(boolean running) { + PreferenceRepository preferences = App.getInstance().getDaggerComponent().getPreferenceRepository().get(); + preferences.setBoolPreference(FIREWALL_RUNNING_PREF, running); + } + public static void stopModulesIfRunning(Context context) { boolean dnsCryptRunning = isDnsCryptSavedStateRunning(); boolean torRunning = isTorSavedStateRunning(); @@ -121,6 +137,13 @@ public static void stopModulesIfRunning(Context context) { if (itpdRunning) { ModulesKiller.stopITPD(context); } + + ModulesStatus.getInstance().setFirewallState( + ModuleState.STOPPED, + App.getInstance().getDaggerComponent().getPreferenceRepository().get() + ); + saveFirewallStateRunning(false); + speedupModulesStateLoopTimer(context); } public static void stopModulesService(Context context) { diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesReceiver.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesReceiver.java index 66fc499f3..e6c5676ec 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesReceiver.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesReceiver.java @@ -35,6 +35,7 @@ import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.DNSCRYPT_DNS64_PREFIX; import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.FIREWALL_ENABLED; import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.GSM_ON_REQUESTED; +import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.IGNORE_SYSTEM_DNS; import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.KILL_SWITCH; import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REFRESH_RULES; import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.VPN_SERVICE_ENABLED; @@ -446,7 +447,7 @@ public void onAvailable(@NonNull Network network) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && lastNetwork != network.hashCode()) { PrivateDnsProxyManager.INSTANCE.checkPrivateDNSAndProxy( - context, null + context, null, isIgnoreSystemDns() ); } @@ -543,7 +544,7 @@ public void onLinkPropertiesChanged(@NonNull Network network, @NonNull LinkPrope if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { PrivateDnsProxyManager.INSTANCE.checkPrivateDNSAndProxy( - context, linkProperties + context, linkProperties, isIgnoreSystemDns() ); } } @@ -746,6 +747,10 @@ private void restartDNSCryptIfRunning() { } } + private boolean isIgnoreSystemDns() { + return defaultPreferences.get().getBoolean(IGNORE_SYSTEM_DNS, false); + } + @TargetApi(Build.VERSION_CODES.LOLLIPOP) private void unlistenNetworkChanges() { ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesService.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesService.java index 25dec7392..faedd0f02 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesService.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesService.java @@ -149,6 +149,7 @@ public class ModulesService extends Service { private ModulesKiller modulesKiller; private UsageStatistic usageStatistic; private ArpScanner arpScanner; + private ModulesServiceNotificationManager serviceNotificationManager; public ModulesService() { } @@ -170,13 +171,9 @@ public void onCreate() { message = usageStatistic.getMessage(System.currentTimeMillis()); } - ModulesServiceNotificationManager serviceNotificationManager = new ModulesServiceNotificationManager( - this, - systemNotificationManager, - UsageStatistics.getStartTime() - ); - serviceNotificationManager.createNotificationChannel(); - serviceNotificationManager.sendNotification(title, message); + serviceNotificationManager = ModulesServiceNotificationManager.getManager(this); + serviceNotificationManager.createNotificationChannel(this); + serviceNotificationManager.sendNotification(this, title, message, UsageStatistics.getStartTime()); } App.getInstance().getDaggerComponent().inject(this); @@ -215,14 +212,17 @@ public int onStartCommand(Intent intent, int flags, int startId) { title = usageStatistic.getTitle(); message = usageStatistic.getMessage(System.currentTimeMillis()); } - - ModulesServiceNotificationManager notification = new ModulesServiceNotificationManager( + if (serviceNotificationManager == null) { + serviceNotificationManager = ModulesServiceNotificationManager + .getManager(this); + } + serviceNotificationManager.sendNotification( this, - systemNotificationManager, + title, + message, UsageStatistics.getStartTime() ); - notification.sendNotification(title, message); - usageStatistic.setServiceNotification(notification); + usageStatistic.setServiceNotification(serviceNotificationManager); if (usageStatistic.isStatisticAllowed()) { usageStatistic.startUpdate(); @@ -870,8 +870,8 @@ void slowdownTimer() { private void makeExtraLoop() { if (timerPeriod != TIMER_HIGH_SPEED && checkModulesStateTask != null) { executor.submit("ModulesService makeExtraLoop", () -> { - checkModulesStateTask.run(); - return null; + checkModulesStateTask.run(); + return null; }); } } @@ -924,6 +924,8 @@ public void onDestroy() { usageStatistic.stopUpdate(); } + ModulesServiceNotificationManager.stopManager(this); + releaseWakelocks(); if (checkModulesStateTask != null && modulesStatus.getMode() == VPN_MODE) { @@ -1145,11 +1147,13 @@ private void cleanLogFileNoRootMethod(String logFilePath, String text) { } private void startArpScanner() { - try { - arpScanner = ArpScanner.getArpComponent().get(); - arpScanner.start(); - } catch (Exception e) { - loge("ModulesService startArpScanner", e); + if (arpScanner == null) { + try { + arpScanner = ArpScanner.getArpComponent().get(); + arpScanner.start(); + } catch (Exception e) { + loge("ModulesService startArpScanner", e); + } } } diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesServiceNotificationManager.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesServiceNotificationManager.java index ed51ed117..a562d8cad 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesServiceNotificationManager.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesServiceNotificationManager.java @@ -19,6 +19,9 @@ package pan.alexander.tordnscrypt.modules; +import static android.content.Context.NOTIFICATION_SERVICE; +import static android.content.Context.RECEIVER_NOT_EXPORTED; + import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.Notification; @@ -26,7 +29,10 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.ServiceInfo; import android.os.Build; @@ -34,43 +40,39 @@ import pan.alexander.tordnscrypt.MainActivity; import pan.alexander.tordnscrypt.R; +import pan.alexander.tordnscrypt.utils.enums.ModuleState; import static pan.alexander.tordnscrypt.modules.ModulesService.DEFAULT_NOTIFICATION_ID; import static pan.alexander.tordnscrypt.utils.logger.Logger.loge; -public class ModulesServiceNotificationManager { +public class ModulesServiceNotificationManager extends BroadcastReceiver { private final static String ANDROID_CHANNEL_ID = "InviZible"; - private final Service service; - private final NotificationManager notificationManager; - private final Long startTime; - private final PendingIntent contentIntent; - private final int iconResource; - - public ModulesServiceNotificationManager(Service service, NotificationManager notificationManager, Long startTime) { - this.service = service; - this.notificationManager = notificationManager; - this.startTime = startTime; - this.contentIntent = getContentIntent(); - this.iconResource = getIconResource(); + private final static String STOP_ALL_ACTION = "pan.alexander.tordnscrypt.NOTIFICATION_STOP_ALL_ACTION"; + private final static int STOP_ALL_ACTION_CODE = 1120; + private static volatile ModulesServiceNotificationManager instance; + + private final ModulesStatus modulesStatus = ModulesStatus.getInstance(); + + public ModulesServiceNotificationManager() { } @SuppressLint("UnspecifiedImmutableFlag") - private PendingIntent getContentIntent() { - Intent notificationIntent = new Intent(service, MainActivity.class); + private PendingIntent getContentIntent(Context context) { + Intent notificationIntent = new Intent(context, MainActivity.class); notificationIntent.setAction(Intent.ACTION_MAIN); notificationIntent.addCategory(Intent.CATEGORY_LAUNCHER); PendingIntent contentIntent; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { contentIntent = PendingIntent.getActivity( - service.getApplicationContext(), + context.getApplicationContext(), 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); } else { contentIntent = PendingIntent.getActivity( - service.getApplicationContext(), + context.getApplicationContext(), 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); @@ -79,12 +81,35 @@ private PendingIntent getContentIntent() { return contentIntent; } + private PendingIntent getStopIntent(Context context) { + + Intent intent = new Intent(context, ModulesServiceNotificationManager.class); + intent.setAction(STOP_ALL_ACTION); + + PendingIntent stopIntent; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + stopIntent = PendingIntent.getBroadcast( + context.getApplicationContext(), + STOP_ALL_ACTION_CODE, + intent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + } else { + stopIntent = PendingIntent.getBroadcast( + context.getApplicationContext(), + STOP_ALL_ACTION_CODE, + intent, + PendingIntent.FLAG_UPDATE_CURRENT); + } + + return stopIntent; + } + @TargetApi(Build.VERSION_CODES.O) - public void createNotificationChannel() { + public void createNotificationChannel(Context context) { NotificationChannel channel = new NotificationChannel( ANDROID_CHANNEL_ID, - service.getString(R.string.notification_channel_services), - NotificationManager.IMPORTANCE_MIN + context.getString(R.string.notification_channel_services), + NotificationManager.IMPORTANCE_LOW ); channel.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT); channel.setDescription(""); @@ -92,50 +117,58 @@ public void createNotificationChannel() { channel.enableVibration(false); channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE); channel.setShowBadge(false); - notificationManager.createNotificationChannel(channel); + getNotificationManager(context).createNotificationChannel(channel); } - private int getIconResource() { + private NotificationManager getNotificationManager(Context context) { + return (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE); + } + + private int getSmallIcon(Context context) { int iconResource = android.R.drawable.ic_menu_view; try { - iconResource = service.getResources().getIdentifier( + iconResource = context.getResources().getIdentifier( "ic_service_notification", "drawable", - service.getPackageName() + context.getPackageName() ); if (iconResource == 0) { iconResource = android.R.drawable.ic_menu_view; } } catch (Exception e) { - loge("ModulesServiceNotificationManager getIconResource", e); + loge("ModulesServiceNotificationManager getSmallIcon", e); } return iconResource; } - public synchronized void sendNotification(String title, String text) { - - if (service == null || notificationManager == null) { - return; - } + public synchronized void sendNotification(Service service, String title, String text, long startTime) { - notificationManager.cancel(DEFAULT_NOTIFICATION_ID); + getNotificationManager(service).cancel(DEFAULT_NOTIFICATION_ID); NotificationCompat.Builder builder = new NotificationCompat.Builder(service, ANDROID_CHANNEL_ID); - builder.setContentIntent(contentIntent) + builder.setContentIntent(getContentIntent(service)) .setOngoing(true) - .setSmallIcon(iconResource) + .setSmallIcon(getSmallIcon(service)) .setContentTitle(title) .setContentText(text) - .setPriority(Notification.PRIORITY_MIN) + .setPriority(Notification.PRIORITY_LOW) .setOnlyAlertOnce(true) .setSilent(true) .setChannelId(ANDROID_CHANNEL_ID) .setVisibility(NotificationCompat.VISIBILITY_PRIVATE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isAnyModuleRunning()) { + builder.addAction( + R.drawable.ic_close_white, + service.getText(R.string.main_fragment_button_stop), + getStopIntent(service) + ); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { builder.setCategory(Notification.CATEGORY_SERVICE); } @@ -159,23 +192,27 @@ public synchronized void sendNotification(String title, String text) { } @SuppressLint("UnspecifiedImmutableFlag") - public void updateNotification(String title, String text) { - if (service == null || notificationManager == null) { - return; - } - - NotificationCompat.Builder builder = new NotificationCompat.Builder(service, ANDROID_CHANNEL_ID); - builder.setContentIntent(contentIntent) + public void updateNotification(Context context, String title, String text, long startTime) { + NotificationCompat.Builder builder = new NotificationCompat.Builder(context, ANDROID_CHANNEL_ID); + builder.setContentIntent(getContentIntent(context)) .setOngoing(true) - .setSmallIcon(iconResource) + .setSmallIcon(getSmallIcon(context)) .setContentTitle(title) .setContentText(text) - .setPriority(Notification.PRIORITY_MIN) + .setPriority(Notification.PRIORITY_LOW) .setOnlyAlertOnce(true) .setSilent(true) .setChannelId(ANDROID_CHANNEL_ID) .setVisibility(NotificationCompat.VISIBILITY_PRIVATE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isAnyModuleRunning()) { + builder.addAction( + R.drawable.ic_close_white, + context.getText(R.string.main_fragment_button_stop), + getStopIntent(context) + ); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { builder.setCategory(Notification.CATEGORY_SERVICE); } @@ -187,6 +224,69 @@ public void updateNotification(String title, String text) { Notification notification = builder.build(); - notificationManager.notify(DEFAULT_NOTIFICATION_ID, notification); + getNotificationManager(context).notify(DEFAULT_NOTIFICATION_ID, notification); + } + + private boolean isAnyModuleRunning() { + return modulesStatus.getDnsCryptState() == ModuleState.RUNNING + || modulesStatus.getTorState() == ModuleState.RUNNING + || modulesStatus.getItpdState() == ModuleState.RUNNING + || modulesStatus.getFirewallState() == ModuleState.RUNNING; + } + + @SuppressLint("UnspecifiedRegisterReceiverFlag") + public static ModulesServiceNotificationManager getManager(Context context) { + if (instance == null) { + synchronized (ModulesServiceNotificationManager.class) { + if (instance == null) { + instance = new ModulesServiceNotificationManager(); + IntentFilter filter = new IntentFilter(STOP_ALL_ACTION); + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + context.getApplicationContext().registerReceiver( + instance, + filter, + RECEIVER_NOT_EXPORTED + ); + } else { + context.getApplicationContext().registerReceiver( + instance, + filter + ); + } + } catch (Exception e) { + loge("ModulesServiceNotificationManager getNotificationManager", e); + } + return instance; + } + } + } + return instance; + } + + public static void stopManager(Context context) { + if (instance != null) { + synchronized (ModulesServiceNotificationManager.class) { + if (instance != null) { + try { + context.getApplicationContext().unregisterReceiver(instance); + } catch (Exception e) { + loge("ModulesServiceNotificationManager stopNotificationManager", e); + } + instance = null; + } + } + } + } + + @Override + public void onReceive(Context context, Intent intent) { + if (context != null && intent != null && STOP_ALL_ACTION.equals(intent.getAction())) { + stopServices(context); + } + } + + private void stopServices(Context context) { + ModulesAux.stopModulesIfRunning(context); } } diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesStateLoop.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesStateLoop.java index 5d419581f..8e717426e 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesStateLoop.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesStateLoop.java @@ -55,6 +55,8 @@ import pan.alexander.tordnscrypt.vpn.service.ServiceVPNHelper; import static pan.alexander.tordnscrypt.di.SharedPreferencesModule.DEFAULT_PREFERENCES_NAME; +import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STARTING; +import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPING; import static pan.alexander.tordnscrypt.utils.logger.Logger.loge; import static pan.alexander.tordnscrypt.utils.logger.Logger.logi; import static pan.alexander.tordnscrypt.utils.logger.Logger.logw; @@ -126,6 +128,7 @@ public class ModulesStateLoop implements Runnable, private ModuleState savedDNSCryptState = UNDEFINED; private ModuleState savedTorState = UNDEFINED; private ModuleState savedItpdState = UNDEFINED; + private ModuleState savedFirewallState = UNDEFINED; private final SharedPreferences sharedPreferences; @@ -184,6 +187,7 @@ public synchronized void run() { modulesStatus.getDnsCryptState(), modulesStatus.getTorState(), modulesStatus.getItpdState(), + modulesStatus.getFirewallState(), operationMode, rootIsAvailable, useModulesWithRoot @@ -204,6 +208,7 @@ public synchronized void run() { logi("ModulesStateLoop stopCounter is zero. Stop service."); modulesStatus.setContextUIDUpdateRequested(false); safeStopModulesService(); + modulesStatus.setFirewallState(STOPPED, preferenceRepository.get()); } slowDownModulesStateTimerIfRequired(); @@ -263,9 +268,14 @@ private void updateFixTTLRules() { } } - private void updateIptablesRules(ModuleState dnsCryptState, ModuleState torState, - ModuleState itpdState, OperationMode operationMode, - boolean rootIsAvailable, boolean useModulesWithRoot) { + private void updateIptablesRules( + ModuleState dnsCryptState, + ModuleState torState, + ModuleState itpdState, + ModuleState firewallState, + OperationMode operationMode, + boolean rootIsAvailable, + boolean useModulesWithRoot) { /* For testing purposes logi(String.format("DNSCrypt is %s Tor is %s I2P is %s\n" + "Operation mode %s Use modules with Root %s " + @@ -277,10 +287,11 @@ private void updateIptablesRules(ModuleState dnsCryptState, ModuleState torState if (dnsCryptState != savedDNSCryptState || torState != savedTorState || itpdState != savedItpdState + || firewallState != savedFirewallState || modulesStatus.isIptablesRulesUpdateRequested()) { - logi(String.format("DNSCrypt is %s Tor is %s I2P is %s\n" + + logi(String.format("DNSCrypt is %s Tor is %s I2P is %s Firewall is %s\n" + "Operation mode %s Use modules with Root %s", - dnsCryptState, torState, itpdState, + dnsCryptState, torState, itpdState, firewallState, operationMode, useModulesWithRoot)); if (dnsCryptState == RESTARTING) { @@ -372,6 +383,14 @@ private void updateIptablesRules(ModuleState dnsCryptState, ModuleState torState } } + if (savedFirewallState != firewallState) { + saveFirewallState(firewallState); + if (firewallState == STARTING || firewallState == RUNNING) { + modulesStatusBroadcaster.get().broadcastFirewallRunning(); + } else if (firewallState == STOPPED) { + modulesStatusBroadcaster.get().broadcastFirewallStopped(); + } + } if (modulesStatus.isIptablesRulesUpdateRequested()) { modulesStatus.setIptablesRulesUpdateRequested(false); @@ -380,7 +399,12 @@ private void updateIptablesRules(ModuleState dnsCryptState, ModuleState torState boolean vpnServiceEnabled = sharedPreferences.getBoolean(VPN_SERVICE_ENABLED, false); if (iptablesRules != null && rootIsAvailable && operationMode == ROOT_MODE) { - List commands = iptablesRules.configureIptables(dnsCryptState, torState, itpdState); + List commands = iptablesRules.configureIptables( + dnsCryptState, + torState, + itpdState, + firewallState + ); int hashCode = commands.hashCode(); if (hashCode == savedIptablesCommandsHash && !iptablesRules.isLastIptablesCommandsReturnError()) { @@ -396,7 +420,8 @@ private void updateIptablesRules(ModuleState dnsCryptState, ModuleState torState stopCounter = STOP_COUNTER_DELAY; } else if (operationMode == VPN_MODE) { - if (dnsCryptState == STOPPED && torState == STOPPED) { + if (dnsCryptState == STOPPED && torState == STOPPED + && (firewallState == STOPPED || firewallState == STOPPING)) { ServiceVPNHelper.stop("All modules stopped", modulesService); } else if (vpnServiceEnabled) { ServiceVPNHelper.reload("Modules state changed", modulesService); @@ -453,7 +478,8 @@ private void updateIptablesRules(ModuleState dnsCryptState, ModuleState torState stopCounter--; } else if ((dnsCryptState == STOPPED || dnsCryptState == FAULT) && (torState == STOPPED || torState == FAULT) - && (itpdState == STOPPED || itpdState == FAULT)) { + && (itpdState == STOPPED || itpdState == FAULT) + && (firewallState == STOPPING || firewallState == STOPPED)) { stopCounter--; } @@ -507,7 +533,10 @@ private void startVPNService() { handler.get().postDelayed(() -> { if (modulesService != null && modulesStatus != null && sharedPreferences != null && !sharedPreferences.getBoolean(VPN_SERVICE_ENABLED, false) - && (modulesStatus.getDnsCryptState() == RUNNING || modulesStatus.getTorState() == RUNNING)) { + && (modulesStatus.getDnsCryptState() == RUNNING + || modulesStatus.getTorState() == RUNNING + || modulesStatus.getFirewallState() == STARTING + || modulesStatus.getFirewallState() == RUNNING)) { sharedPreferences.edit().putBoolean(VPN_SERVICE_ENABLED, true).apply(); ServiceVPNHelper.start("ModulesStateLoop start VPN service", modulesService); } @@ -660,6 +689,15 @@ private void setITPDReady(boolean ready) { } } + private void saveFirewallState(ModuleState firewallState) { + savedFirewallState = firewallState; + if (firewallState == RUNNING) { + ModulesAux.saveFirewallStateRunning(true); + } else if (firewallState == STOPPED) { + ModulesAux.saveFirewallStateRunning(false); + } + } + @Override public boolean isActive() { return ModulesService.serviceIsRunning; diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesStatus.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesStatus.java index a10d68544..8b80f8885 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesStatus.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesStatus.java @@ -23,10 +23,14 @@ import androidx.annotation.NonNull; +import pan.alexander.tordnscrypt.domain.preferences.PreferenceRepository; import pan.alexander.tordnscrypt.utils.enums.ModuleState; import pan.alexander.tordnscrypt.utils.enums.OperationMode; +import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPED; import static pan.alexander.tordnscrypt.utils.enums.ModuleState.UNDEFINED; +import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.FIREWALL_ENABLED; +import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.FIREWALL_WAS_STARTED; public final class ModulesStatus { @@ -34,6 +38,8 @@ public final class ModulesStatus { private volatile ModuleState torState = UNDEFINED; private volatile ModuleState itpdState = UNDEFINED; + private volatile ModuleState firewallState = STOPPED; + private volatile boolean rootAvailable = false; private volatile boolean useModulesWithRoot; private volatile boolean requestIptablesUpdate; @@ -80,6 +86,10 @@ public ModuleState getItpdState() { return itpdState; } + public ModuleState getFirewallState() { + return firewallState; + } + public void setDnsCryptState(ModuleState dnsCryptState) { this.dnsCryptState = dnsCryptState; } @@ -92,6 +102,17 @@ public void setItpdState(ModuleState itpdState) { this.itpdState = itpdState; } + public void setFirewallState(ModuleState firewallState, PreferenceRepository preferenceRepository) { + boolean firewallEnabled = preferenceRepository.getBoolPreference(FIREWALL_ENABLED) + && preferenceRepository.getBoolPreference(FIREWALL_WAS_STARTED); + if (firewallEnabled + && (mode == OperationMode.VPN_MODE || mode == OperationMode.ROOT_MODE)) { + this.firewallState = firewallState; + } else { + this.firewallState = STOPPED; + } + } + public boolean isUseModulesWithRoot() { return useModulesWithRoot; } diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesStatusBroadcaster.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesStatusBroadcaster.kt index 3789d2154..c11b6f9fe 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesStatusBroadcaster.kt +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesStatusBroadcaster.kt @@ -22,6 +22,7 @@ package pan.alexander.tordnscrypt.modules import android.content.Context import android.content.Intent import android.content.SharedPreferences +import androidx.localbroadcastmanager.content.LocalBroadcastManager import pan.alexander.tordnscrypt.di.SharedPreferencesModule.Companion.DEFAULT_PREFERENCES_NAME import pan.alexander.tordnscrypt.settings.PathVars import pan.alexander.tordnscrypt.utils.logger.Logger.logi @@ -30,28 +31,6 @@ import javax.inject.Inject import javax.inject.Named import javax.inject.Singleton -private const val STATUS_ACTION = "pan.alexander.tordnscrypt.STATUS_ACTION" - -private const val STATUS_ARG = "STATUS" -private const val STATUS_RUNNING = "RUNNING" -private const val STATUS_READY = "READY" -private const val STATUS_STOPPED = "STOPPED" -private const val STATUS_DISABLED = "CONTROL_DISABLED" - -private const val MODULE_ARG = "MODULE" -private const val DNSCRYPT = "DNSCRYPT" -private const val TOR = "TOR" -private const val I2PD = "I2PD" - -private const val DNSCRYPT_DNS_PORT_ARG = "DNSCRYPT_DNS_PORT" - -private const val TOR_DNS_PORT_ARG = "TOR_DNS_PORT" -private const val TOR_SOCKS_PROXY_PORT_ARG = "TOR_SOCKS_PROXY_PORT" -private const val TOR_TRANSPARENT_PROXY_PORT_ARG = "TOR_TRANSPARENT_PROXY_PORT" -private const val TOR_HTTP_PROXY_PORT_ARG = "TOR_HTTP_PROXY_PORT" - -private const val I2PD_HTTP_PROXY_PORT_ARG = "I2PD_HTTP_PROXY_PORT" - @Singleton class ModulesStatusBroadcaster @Inject constructor( private val context: Context, @@ -154,6 +133,20 @@ class ModulesStatusBroadcaster @Inject constructor( } } + fun broadcastFirewallRunning() { + getFirewallIntent().also { + it.putExtra(STATUS_ARG, STATUS_RUNNING) + LocalBroadcastManager.getInstance(context).sendBroadcast(it) + } + } + + fun broadcastFirewallStopped() { + getFirewallIntent().also { + it.putExtra(STATUS_ARG, STATUS_STOPPED) + LocalBroadcastManager.getInstance(context).sendBroadcast(it) + } + } + private fun getDNSCryptIntent() = Intent().also { it.setAction(STATUS_ACTION) @@ -178,6 +171,12 @@ class ModulesStatusBroadcaster @Inject constructor( it.putExtra(I2PD_HTTP_PROXY_PORT_ARG, pathVars.itpdHttpProxyPort) } + private fun getFirewallIntent() = + Intent().also { + it.setAction(STATUS_ACTION) + it.putExtra(MODULE_ARG, FIREWALL) + } + fun broadcastRemoteControlDisabled() { Intent().also { it.setAction(STATUS_ACTION) @@ -190,4 +189,29 @@ class ModulesStatusBroadcaster @Inject constructor( remoteControlActive = enabled } + companion object { + const val STATUS_ACTION = "pan.alexander.tordnscrypt.STATUS_ACTION" + + const val STATUS_ARG = "STATUS" + const val STATUS_RUNNING = "RUNNING" + const val STATUS_READY = "READY" + const val STATUS_STOPPED = "STOPPED" + const val STATUS_DISABLED = "CONTROL_DISABLED" + + const val MODULE_ARG = "MODULE" + const val DNSCRYPT = "DNSCRYPT" + const val TOR = "TOR" + const val I2PD = "I2PD" + const val FIREWALL = "FIREWALL" + + const val DNSCRYPT_DNS_PORT_ARG = "DNSCRYPT_DNS_PORT" + + const val TOR_DNS_PORT_ARG = "TOR_DNS_PORT" + const val TOR_SOCKS_PROXY_PORT_ARG = "TOR_SOCKS_PROXY_PORT" + const val TOR_TRANSPARENT_PROXY_PORT_ARG = "TOR_TRANSPARENT_PROXY_PORT" + const val TOR_HTTP_PROXY_PORT_ARG = "TOR_HTTP_PROXY_PORT" + + const val I2PD_HTTP_PROXY_PORT_ARG = "I2PD_HTTP_PROXY_PORT" + } + } diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/UsageStatistic.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/UsageStatistic.kt index 924c0fbd9..76496eba1 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/UsageStatistic.kt +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/UsageStatistic.kt @@ -122,7 +122,7 @@ class UsageStatistic(private val context: Context) { if (modulesStatus.isDeviceInteractive && (title != savedTitle || message != savedMessage)) { - serviceNotification?.updateNotification(title, message) + serviceNotification?.updateNotification(context, title, message, startTime) savedTitle = title savedMessage = message } diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/PathVars.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/PathVars.java index af920fe5b..184b3d0fe 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/PathVars.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/PathVars.java @@ -312,6 +312,10 @@ public String getDNSCryptBlackListPath() { return appDataDir + "/app_data/dnscrypt-proxy/blacklist.txt"; } + public String getDNSCryptSingleBlackListPath() { + return appDataDir + "/app_data/dnscrypt-proxy/blacklist-single.txt"; + } + public String getDNSCryptLocalBlackListPath() { return appDataDir + "/app_data/dnscrypt-proxy/blacklist-local.txt"; } @@ -324,6 +328,10 @@ public String getDNSCryptIPBlackListPath() { return appDataDir + "/app_data/dnscrypt-proxy/ip-blacklist.txt"; } + public String getDNSCryptSingleIPBlackListPath() { + return appDataDir + "/app_data/dnscrypt-proxy/ip-blacklist-single.txt"; + } + public String getDNSCryptLocalIPBlackListPath() { return appDataDir + "/app_data/dnscrypt-proxy/ip-blacklist-local.txt"; } @@ -336,6 +344,10 @@ public String getDNSCryptWhiteListPath() { return appDataDir + "/app_data/dnscrypt-proxy/whitelist.txt"; } + public String getDNSCryptSingleWhiteListPath() { + return appDataDir + "/app_data/dnscrypt-proxy/whitelist-single.txt"; + } + public String getDNSCryptLocalWhiteListPath() { return appDataDir + "/app_data/dnscrypt-proxy/whitelist-local.txt"; } @@ -348,6 +360,10 @@ public String getDNSCryptCloakingRulesPath() { return appDataDir + "/app_data/dnscrypt-proxy/cloaking-rules.txt"; } + public String getDNSCryptSingleCloakingRulesPath() { + return appDataDir + "/app_data/dnscrypt-proxy/cloaking-rules-single.txt"; + } + public String getDNSCryptLocalCloakingRulesPath() { return appDataDir + "/app_data/dnscrypt-proxy/cloaking-rules-local.txt"; } @@ -360,6 +376,10 @@ public String getDNSCryptForwardingRulesPath() { return appDataDir + "/app_data/dnscrypt-proxy/forwarding-rules.txt"; } + public String getDNSCryptSingleForwardingRulesPath() { + return appDataDir + "/app_data/dnscrypt-proxy/forwarding-rules-single.txt"; + } + public String getDNSCryptLocalForwardingRulesPath() { return appDataDir + "/app_data/dnscrypt-proxy/forwarding-rules-local.txt"; } @@ -472,4 +492,12 @@ public void setAppVersion(T ignoredCaller, String versio public String getAppProcVersion() { return appProcVersion; } + + public String getDNSCryptDefaultForwardingRule() { + return "onion 127.0.0.1:" + getTorDNSPort(); + } + + public String getDNSCryptDefaultCloakingRule() { + return "*.i2p 10.191.0.1"; + } } diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/PreferencesCommonFragment.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/PreferencesCommonFragment.java index dcfb81ed8..7b9298b69 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/PreferencesCommonFragment.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/PreferencesCommonFragment.java @@ -43,7 +43,7 @@ import android.view.View; import android.view.ViewGroup; -import java.util.ArrayList;; +import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -79,6 +79,7 @@ import static pan.alexander.tordnscrypt.utils.Constants.META_ADDRESS; import static pan.alexander.tordnscrypt.utils.logger.Logger.loge; import static pan.alexander.tordnscrypt.utils.logger.Logger.logi; +import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.ALWAYS_ON_VPN; import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.ARP_SPOOFING_BLOCK_INTERNET; import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.ARP_SPOOFING_DETECTION; import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.ARP_SPOOFING_NOT_SUPPORTED; @@ -180,6 +181,13 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, } } + Preference alwaysOnVPN = findPreference(ALWAYS_ON_VPN); + if (modulesStatus.getMode() == VPN_MODE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O + && alwaysOnVPN != null) { + alwaysOnVPN.setOnPreferenceClickListener(this); + } else if (otherCategory != null && alwaysOnVPN != null) { + otherCategory.removePreference(alwaysOnVPN); + } Preference swCompatibilityMode = findPreference(COMPATIBILITY_MODE); if (modulesStatus.getMode() != VPN_MODE && otherCategory != null && swCompatibilityMode != null) { @@ -485,6 +493,13 @@ public boolean onPreferenceClick(@NonNull Preference preference) { } catch (Exception e) { loge("PreferencesCommonFragment startHOTSPOT", e); } + } else if (ALWAYS_ON_VPN.equals(preference.getKey())) { + Intent vpnSettingsIntent = new Intent("android.settings.VPN_SETTINGS"); + try { + getActivity().startActivity(vpnSettingsIntent); + } catch (Exception e) { + loge("PreferencesCommonFragment ALWAYS_ON_VPN", e); + } } return false; } diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/SettingsActivity.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/SettingsActivity.java index f31e3be96..2453fd02b 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/SettingsActivity.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/SettingsActivity.java @@ -20,25 +20,18 @@ package pan.alexander.tordnscrypt.settings; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.appcompat.widget.SearchView; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.FragmentTransaction; import androidx.fragment.app.Fragment; import android.annotation.SuppressLint; -import android.content.ClipData; import android.content.Intent; -import android.content.SharedPreferences; -import android.net.Uri; import android.os.Bundle; -import androidx.preference.PreferenceManager; - import android.view.Menu; import android.view.MenuItem; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -53,12 +46,13 @@ import pan.alexander.tordnscrypt.settings.dnscrypt_servers.PreferencesDNSCryptServers; import pan.alexander.tordnscrypt.settings.dnscrypt_settings.PreferencesDNSFragment; import pan.alexander.tordnscrypt.settings.firewall.FirewallFragment; +import pan.alexander.tordnscrypt.domain.dns_rules.DnsRuleType; +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.DnsRulesFragment; import pan.alexander.tordnscrypt.settings.tor_bridges.PreferencesTorBridges; -import pan.alexander.tordnscrypt.settings.show_rules.ShowRulesRecycleFrag; +import pan.alexander.tordnscrypt.settings.itpd_settings.ITPDSubscriptionsFragment; import pan.alexander.tordnscrypt.settings.tor_apps.UnlockTorAppsFragment; import pan.alexander.tordnscrypt.settings.tor_ips.UnlockTorIpsFragment; import pan.alexander.tordnscrypt.settings.tor_preferences.PreferencesTorFragment; -import pan.alexander.tordnscrypt.utils.enums.DNSCryptRulesVariant; import pan.alexander.tordnscrypt.utils.filemanager.FileManager; import static pan.alexander.tordnscrypt.settings.tor_ips.UnlockTorIpsFragment.DeviceOrTether.DEVICE; @@ -75,7 +69,6 @@ public class SettingsActivity extends LangAppCompatActivity { public static final String tor_conf_tag = "pan.alexander.tordnscrypt/app_data/tor/tor.conf"; public static final String itpd_conf_tag = "pan.alexander.tordnscrypt/app_data/itpd/itpd.conf"; public static final String itpd_tunnels_tag = "pan.alexander.tordnscrypt/app_data/itpd/tunnels.conf"; - public static final String rules_tag = "pan.alexander.tordnscrypt/app_data/abstract_rules"; @Inject public Lazy preferenceRepository; @@ -156,45 +149,42 @@ protected void onCreate(Bundle savedInstanceState) { fSupportTrans.replace(android.R.id.content, frag); fSupportTrans.commit(); } else if (Objects.equals(intent.getAction(), "forwarding_rules_Pref")) { - dialogFragment = PleaseWaitProgressDialog.getInstance(); - dialogFragment.show(getSupportFragmentManager(), "PleaseWaitProgressDialog"); - FileManager.readTextFile(this, appDataDir + "/app_data/dnscrypt-proxy/forwarding-rules.txt", rules_tag); + Bundle bundle = new Bundle(); + bundle.putSerializable(DnsRulesFragment.RULE_TYPE_ARG, DnsRuleType.FORWARDING); + DnsRulesFragment fragment = new DnsRulesFragment(); + fragment.setArguments(bundle); + fSupportTrans.replace(android.R.id.content, fragment); + fSupportTrans.commit(); } else if (Objects.equals(intent.getAction(), "cloaking_rules_Pref")) { - dialogFragment = PleaseWaitProgressDialog.getInstance(); - dialogFragment.show(getSupportFragmentManager(), "PleaseWaitProgressDialog"); - FileManager.readTextFile(this, appDataDir + "/app_data/dnscrypt-proxy/cloaking-rules.txt", rules_tag); + Bundle bundle = new Bundle(); + bundle.putSerializable(DnsRulesFragment.RULE_TYPE_ARG, DnsRuleType.CLOAKING); + DnsRulesFragment fragment = new DnsRulesFragment(); + fragment.setArguments(bundle); + fSupportTrans.replace(android.R.id.content, fragment); + fSupportTrans.commit(); } else if (Objects.equals(intent.getAction(), "blacklist_Pref")) { - dialogFragment = PleaseWaitProgressDialog.getInstance(); - dialogFragment.show(getSupportFragmentManager(), "PleaseWaitProgressDialog"); - FileManager.readTextFile(this, appDataDir + "/app_data/dnscrypt-proxy/blacklist.txt", rules_tag); + Bundle bundle = new Bundle(); + bundle.putSerializable(DnsRulesFragment.RULE_TYPE_ARG, DnsRuleType.BLACKLIST); + DnsRulesFragment fragment = new DnsRulesFragment(); + fragment.setArguments(bundle); + fSupportTrans.replace(android.R.id.content, fragment); + fSupportTrans.commit(); } else if (Objects.equals(intent.getAction(), "ipblacklist_Pref")) { - dialogFragment = PleaseWaitProgressDialog.getInstance(); - dialogFragment.show(getSupportFragmentManager(), "PleaseWaitProgressDialog"); - FileManager.readTextFile(this, appDataDir + "/app_data/dnscrypt-proxy/ip-blacklist.txt", rules_tag); + Bundle bundle = new Bundle(); + bundle.putSerializable(DnsRulesFragment.RULE_TYPE_ARG, DnsRuleType.IP_BLACKLIST); + DnsRulesFragment fragment = new DnsRulesFragment(); + fragment.setArguments(bundle); + fSupportTrans.replace(android.R.id.content, fragment); + fSupportTrans.commit(); } else if (Objects.equals(intent.getAction(), "whitelist_Pref")) { - dialogFragment = PleaseWaitProgressDialog.getInstance(); - dialogFragment.show(getSupportFragmentManager(), "PleaseWaitProgressDialog"); - FileManager.readTextFile(this, appDataDir + "/app_data/dnscrypt-proxy/whitelist.txt", rules_tag); - } else if (Objects.equals(intent.getAction(), "pref_itpd_addressbook_subscriptions")) { - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); - ArrayList rules_file = new ArrayList<>(); - - String subscriptionsSaved = sp.getString("subscriptions", ""); - - String[] arr = {""}; - if (subscriptionsSaved != null && subscriptionsSaved.contains(",")) { - arr = subscriptionsSaved.split(","); - } - - String subscriptions = "subscriptions"; - for (String str : arr) { - rules_file.add(str.trim()); - } Bundle bundle = new Bundle(); - bundle.putStringArrayList("rules_file", rules_file); - bundle.putString("path", subscriptions); - ShowRulesRecycleFrag frag = new ShowRulesRecycleFrag(); - frag.setArguments(bundle); + bundle.putSerializable(DnsRulesFragment.RULE_TYPE_ARG, DnsRuleType.WHITELIST); + DnsRulesFragment fragment = new DnsRulesFragment(); + fragment.setArguments(bundle); + fSupportTrans.replace(android.R.id.content, fragment); + fSupportTrans.commit(); + } else if (Objects.equals(intent.getAction(), "pref_itpd_addressbook_subscriptions")) { + ITPDSubscriptionsFragment frag = new ITPDSubscriptionsFragment(); fSupportTrans.replace(android.R.id.content, frag); fSupportTrans.commit(); } else if (Objects.equals(intent.getAction(), "tor_sites_unlock")) { @@ -243,60 +233,6 @@ public void onAttachFragment(@NonNull Fragment fragment) { } } - @Override - protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - if (resultCode != RESULT_OK) { - return; - } - - if (preferencesDNSFragment != null && data != null) { - Uri[] filesUri = getFilesUri(data); - - if (filesUri.length > 0) { - - switch (requestCode) { - case PreferencesDNSFragment.PICK_BLACKLIST_HOSTS: - preferencesDNSFragment.importRules(this, DNSCryptRulesVariant.BLACKLIST_HOSTS, filesUri); - break; - case PreferencesDNSFragment.PICK_WHITELIST_HOSTS: - preferencesDNSFragment.importRules(this, DNSCryptRulesVariant.WHITELIST_HOSTS, filesUri); - break; - case PreferencesDNSFragment.PICK_BLACKLIST_IPS: - preferencesDNSFragment.importRules(this, DNSCryptRulesVariant.BLACKLIST_IPS, filesUri); - break; - case PreferencesDNSFragment.PICK_FORWARDING: - preferencesDNSFragment.importRules(this, DNSCryptRulesVariant.FORWARDING, filesUri); - break; - case PreferencesDNSFragment.PICK_CLOAKING: - preferencesDNSFragment.importRules(this, DNSCryptRulesVariant.CLOAKING, filesUri); - break; - default: - loge("SettingsActivity wrong onActivityRequestCode " + requestCode); - } - - } - } - } - - private Uri[] getFilesUri(Intent data) { - Uri[] uris = new Uri[0]; - ClipData clipData = data.getClipData(); - - if (clipData == null && data.getData() != null) { - uris = new Uri[]{data.getData()}; - } else if (clipData != null) { - uris = new Uri[clipData.getItemCount()]; - for (int i = 0; i < clipData.getItemCount(); i++) { - Uri uri = clipData.getItemAt(i).getUri(); - uris[i] = uri; - } - } - - return uris; - } - @Override protected void onDestroy() { super.onDestroy(); diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/SettingsParser.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/SettingsParser.java index fd73727a5..da63b9dcd 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/SettingsParser.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/SettingsParser.java @@ -33,7 +33,7 @@ import pan.alexander.tordnscrypt.R; import pan.alexander.tordnscrypt.settings.dnscrypt_settings.PreferencesDNSFragment; -import pan.alexander.tordnscrypt.settings.show_rules.ShowRulesRecycleFrag; +import pan.alexander.tordnscrypt.settings.itpd_settings.PreferencesITPDFragment; import pan.alexander.tordnscrypt.settings.tor_preferences.PreferencesTorFragment; import pan.alexander.tordnscrypt.utils.enums.FileOperationsVariants; import pan.alexander.tordnscrypt.utils.filemanager.FileManager; @@ -44,7 +44,6 @@ import static pan.alexander.tordnscrypt.utils.Constants.IPv6_REGEX_WITH_MASK; import static pan.alexander.tordnscrypt.utils.logger.Logger.loge; import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.DNSCRYPT_BOOTSTRAP_RESOLVERS; -import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.DNSCRYPT_DNS64_PREFIX; import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.DNSCRYPT_LISTEN_PORT; import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.DNSCRYPT_OUTBOUND_PROXY; import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.DORMANT_CLIENT_TIMEOUT; @@ -430,27 +429,6 @@ private void readITPDconf(List lines) { } } - private void readRules(String path, List lines) { - ArrayList rules_file = new ArrayList<>(); - if (lines != null) { - rules_file.addAll(lines); - } else { - rules_file.add(""); - } - FragmentManager manager = settingsActivity.getSupportFragmentManager(); - if (manager.isDestroyed()) { - return; - } - FragmentTransaction fTrans = manager.beginTransaction(); - Bundle bundle = new Bundle(); - bundle.putStringArrayList("rules_file", rules_file); - bundle.putString("path", path); - ShowRulesRecycleFrag frag = new ShowRulesRecycleFrag(); - frag.setArguments(bundle); - fTrans.replace(android.R.id.content, frag); - fTrans.commit(); - } - public void activateSettingsParser() { FileManager.setOnFileOperationCompleteListener(this); } @@ -483,17 +461,10 @@ public void OnFileOperationComplete(FileOperationsVariants currentFileOperation, case SettingsActivity.itpd_conf_tag: readITPDconf(lines); break; - case SettingsActivity.rules_tag: - readRules(path, lines); - break; } }); - } else if (!fileOperationResult && currentFileOperation == readTextFile) { - if (tag.equals(SettingsActivity.rules_tag)) { - readRules(path, lines); - } } else if (fileOperationResult && currentFileOperation == writeToTextFile) { settingsActivity.runOnUiThread(() -> Toast.makeText(settingsActivity, settingsActivity.getText(R.string.toastSettings_saved), Toast.LENGTH_SHORT).show()); } diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/AddRemoteRulesUrlDialog.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/AddRemoteRulesUrlDialog.kt new file mode 100644 index 000000000..d7af4f985 --- /dev/null +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/AddRemoteRulesUrlDialog.kt @@ -0,0 +1,87 @@ +/* + This file is part of InviZible Pro. + + InviZible Pro is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + InviZible Pro is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with InviZible Pro. If not, see . + + Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com + */ + +package pan.alexander.tordnscrypt.settings.dnscrypt_rules + +import android.content.Context +import android.text.Editable +import android.text.TextWatcher +import android.widget.EditText +import androidx.appcompat.app.AlertDialog +import androidx.core.content.ContextCompat +import androidx.core.view.setPadding +import pan.alexander.tordnscrypt.R +import pan.alexander.tordnscrypt.utils.Constants.URL_REGEX +import pan.alexander.tordnscrypt.utils.Utils.dp2pixels +import pan.alexander.tordnscrypt.utils.Utils.getDomainNameFromUrl +import java.util.regex.Pattern + +class AddRemoteRulesUrlDialog { + + var callback: OnAddRemoteRulesUrl? = null + + fun createDialog(context: Context) = + AlertDialog.Builder(context) + .apply { + val urlPattern = Pattern.compile(URL_REGEX) + val editText = EditText(context).apply { + setPadding(dp2pixels(8).toInt()) + addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged( + s: CharSequence?, + start: Int, + count: Int, + after: Int + ) { + } + + override fun onTextChanged( + s: CharSequence?, + start: Int, + before: Int, + count: Int + ) { + } + + override fun afterTextChanged(s: Editable?) { + if (urlPattern.matcher(s?.toString() ?: "").matches()) { + setTextColor(ContextCompat.getColor(context, R.color.colorText)) + } else { + setTextColor(ContextCompat.getColor(context, R.color.colorAlert)) + } + } + + }) + } + setView(editText) + setTitle(R.string.dns_rule_add_url) + setPositiveButton(R.string.ok) { _, _ -> + val url = editText.text?.toString() ?: "" + if (urlPattern.matcher(url).matches()) { + val name = getDomainNameFromUrl(url) + callback?.onRemoteRulesUrlAdded(url, name) + } + } + setNegativeButton(R.string.cancel) { _, _ -> } + }.create() + + interface OnAddRemoteRulesUrl { + fun onRemoteRulesUrlAdded(url: String, name: String) + } +} diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/DnsRulesFragment.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/DnsRulesFragment.kt new file mode 100644 index 000000000..2c57447bc --- /dev/null +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/DnsRulesFragment.kt @@ -0,0 +1,404 @@ +/* + This file is part of InviZible Pro. + + InviZible Pro is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + InviZible Pro is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with InviZible Pro. If not, see . + + Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com + */ + +package pan.alexander.tordnscrypt.settings.dnscrypt_rules + +import android.content.Context +import android.os.Build +import android.os.Bundle +import android.os.Environment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AlertDialog +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.flowWithLifecycle +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.LinearLayoutManager +import com.github.angads25.filepicker.model.DialogConfigs +import com.github.angads25.filepicker.model.DialogProperties +import com.github.angads25.filepicker.view.FilePickerDialog +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch +import pan.alexander.tordnscrypt.App +import pan.alexander.tordnscrypt.R +import pan.alexander.tordnscrypt.databinding.FragmentDnsRuleBinding +import pan.alexander.tordnscrypt.settings.PathVars +import pan.alexander.tordnscrypt.domain.dns_rules.DnsRuleType +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.recycler.DnsRuleRecycleItem +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.recycler.DnsRulesRecyclerAdapter +import pan.alexander.tordnscrypt.utils.logger.Logger.loge +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.receiver.DnsRulesReceiver +import java.io.File +import java.util.Date +import javax.inject.Inject + +private const val TOTAL_RULES_WARNING_COUNT = 400000 +private const val TOTAL_RULES_ALERT_COUNT = 800000 + +class DnsRulesFragment : Fragment(), DnsRulesReceiver.Callback, + AddRemoteRulesUrlDialog.OnAddRemoteRulesUrl { + + @Inject + lateinit var pathVars: dagger.Lazy + + @Inject + lateinit var dnsRulesReceiver: dagger.Lazy + + @Inject + lateinit var viewModelFactory: ViewModelProvider.Factory + private val viewModel by lazy { + ViewModelProvider(this, viewModelFactory)[DnsRulesViewModel::class.java] + } + + private var _binding: FragmentDnsRuleBinding? = null + private val binding get() = _binding!! + + private var rulesAdapter: DnsRulesRecyclerAdapter? = null + + @Suppress("deprecation") + private val ruleType by lazy { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + arguments?.getSerializable(RULE_TYPE_ARG, DnsRuleType::class.java) + } else { + arguments?.getSerializable(RULE_TYPE_ARG) as DnsRuleType + } + } + + private var importFilesLauncher: ActivityResultLauncher>? = null + + override fun onCreate(savedInstanceState: Bundle?) { + App.instance.daggerComponent.inject(this) + super.onCreate(savedInstanceState) + + importFilesLauncher = + activity?.registerForActivityResult(ActivityResultContracts.OpenMultipleDocuments()) { + importRulesCommon(it.toTypedArray()) + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + + _binding = try { + FragmentDnsRuleBinding.inflate(inflater, container, false) + } catch (e: Exception) { + loge("DnsRulesFragment onCreateView", e) + throw e + } + + ruleType?.let { + setTitle(it) + } + + initRulesRecycler() + + return binding.root + } + + private fun setTitle(ruleType: DnsRuleType) { + when (ruleType) { + DnsRuleType.BLACKLIST -> requireActivity().setTitle(R.string.title_dnscrypt_blacklist) + DnsRuleType.WHITELIST -> requireActivity().setTitle(R.string.title_dnscrypt_whitelist) + DnsRuleType.IP_BLACKLIST -> requireActivity().setTitle(R.string.title_dnscrypt_ip_blacklist) + DnsRuleType.FORWARDING -> requireActivity().setTitle(R.string.title_dnscrypt_forwarding_rules) + DnsRuleType.CLOAKING -> requireActivity().setTitle(R.string.title_dnscrypt_cloaking_rules) + } + } + + private fun initRulesRecycler() { + rulesAdapter = DnsRulesRecyclerAdapter( + ::importLocalRules, + ::deleteLocalRules, + ::deltaTotalRules, + ::addRemoteRules, + ::deleteRemoteRules, + ::refreshRemoteRules + ).also { + it.rulesType = ruleType + } + binding.rvDnsRules.layoutManager = LinearLayoutManager(context) + binding.rvDnsRules.adapter = rulesAdapter + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + registerReceivers() + observeRules() + + if (savedInstanceState == null) { + requestRules() + } + } + + private fun registerReceivers() = with(dnsRulesReceiver.get()) { + callback = this@DnsRulesFragment + dnsRulesReceiver.get().registerReceiver() + } + + private fun requestRules() { + ruleType?.let { + viewModel.requestRules(it) + showProgressIndicator() + } + } + + private fun observeRules() { + lifecycleScope.launch { + viewModel.dnsRulesStateFlow + .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED) + .collectLatest { + it?.let { rules -> + updateRules(rules) + }.also { + updateTotalRules(viewModel.totalRulesCount.get()) + } + } + } + } + + private suspend fun updateRules(rules: List) { + while (binding.rvDnsRules.isComputingLayout) { + delay(300) + } + hideProgressIndicator() + rulesAdapter?.updateRules(rules) + hideProgressIndicator() + } + + + private fun deltaTotalRules(delta: Int) { + updateTotalRules(viewModel.totalRulesCount.addAndGet(delta)) + } + + private fun updateTotalRules(count: Int) { + binding.tvDnsRuleTotalQuantity.text = String.format( + getString(R.string.total_rules), + count + ) + when (count) { + in 0..TOTAL_RULES_WARNING_COUNT -> { + binding.tvDnsRuleTotalQuantity.setBackgroundColor( + ContextCompat.getColor(requireContext(), R.color.colorGreen) + ) + } + + in TOTAL_RULES_WARNING_COUNT..TOTAL_RULES_ALERT_COUNT -> { + binding.tvDnsRuleTotalQuantity.setBackgroundColor( + ContextCompat.getColor(requireContext(), R.color.colorOrange) + ) + } + + else -> { + binding.tvDnsRuleTotalQuantity.setBackgroundColor( + ContextCompat.getColor(requireContext(), R.color.colorAlert) + ) + } + } + } + + private fun addRemoteRules() { + val context = context ?: return + getAddUrlDialog(context).show() + } + + private fun getAddUrlDialog(context: Context): AlertDialog = + AddRemoteRulesUrlDialog().also { + it.callback = this + }.createDialog(context) + + override fun onRemoteRulesUrlAdded(url: String, name: String) { + saveRemoteRulesUrl(url) + rulesAdapter?.updateRemoteRules( + DnsRuleRecycleItem.DnsRemoteRule( + name = name, + url = url, + date = Date(), + count = 0, + size = 0, + inProgress = true + ) + ) + processRemoteRules(name) + binding.rvDnsRules.smoothScrollToPosition(0) + } + + private fun saveRemoteRulesUrl(url: String) { + val ruleType = ruleType ?: return + viewModel.saveRemoteRulesUrl(ruleType, url) + } + + private fun processRemoteRules(name: String) { + val ruleType = ruleType ?: return + val currentRules = getCurrentRules() ?: return + viewModel.updateRemoteRules(ruleType, currentRules, name) + } + + private fun refreshRemoteRules() { + val ruleType = ruleType ?: return + val currentRules = getCurrentRules() ?: return + val remoteRules = getCurrentRemoteRules() ?: return + viewModel.updateRemoteRules(ruleType, currentRules, remoteRules.name) + } + + private fun deleteRemoteRules() { + val ruleType = ruleType ?: return + val currentRules = getCurrentRules() ?: return + viewModel.deleteRemoteRules(ruleType, currentRules) + } + + private fun importLocalRules() { + lifecycleScope.launch { + if (viewModel.isExternalStorageAllowsDirectAccess()) { + importRulesWithFilePicker() + } else { + importRulesWithSAF() + } + } + } + + private fun importRulesWithFilePicker() { + val activity = activity ?: return + val dialog = FilePickerDialog( + activity, + getFilePickerProperties(activity) + ) + dialog.setDialogSelectionListener { + importRulesCommon(it) + } + dialog.show() + } + + private fun importRulesWithSAF() { + try { + importFilesLauncher?.launch(arrayOf("text/plain")) + } catch (e: Exception) { + loge("DnsRulesFragment importRulesWithSAF", e) + } + } + + private fun importRulesCommon(files: Array<*>) { + val ruleType = ruleType ?: return + val currentRules = getCurrentRules() ?: return + viewModel.importLocalRules(ruleType, currentRules, files) + } + + private fun getFilePickerProperties(context: Context): DialogProperties = + DialogProperties().apply { + selection_mode = DialogConfigs.MULTI_MODE + selection_type = DialogConfigs.FILE_SELECT + root = + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + error_dir = File(pathVars.get().getCacheDirPath(context)) + offset = + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + extensions = arrayOf("txt") + } + + private fun deleteLocalRules() { + val ruleType = ruleType ?: return + val currentRules = getCurrentRules() ?: return + viewModel.deleteLocalRules(ruleType, currentRules) + } + + private fun getCurrentRules() = rulesAdapter?.getRules()?.toList() + + private fun getCurrentRemoteRules() = rulesAdapter?.getRules()?.find { + it is DnsRuleRecycleItem.DnsRemoteRule + } as? DnsRuleRecycleItem.DnsRemoteRule + + override fun onPause() { + super.onPause() + + val activity = activity ?: return + + getCurrentRules()?.let { + if (activity.isFinishing) { + saveRulesPersistent(it) + } else { + saveRulesTemporarily(it) + } + } + } + + private fun saveRulesTemporarily(rules: List) { + viewModel.saveTemporarily(rules) + } + + private fun saveRulesPersistent(rules: List) { + val ruleType = ruleType ?: return + viewModel.saveRules(ruleType, rules) + } + + override fun onDestroyView() { + super.onDestroyView() + + unregisterReceiver() + _binding = null + rulesAdapter = null + } + + private fun unregisterReceiver() = with(dnsRulesReceiver.get()) { + callback = null + unregisterReceiver() + } + + private fun showProgressIndicator() { + binding.pbDnsRules.visibility = View.VISIBLE + } + + private fun hideProgressIndicator() { + binding.pbDnsRules.visibility = View.GONE + } + + override fun onUpdateRemoteRules(rules: DnsRuleRecycleItem.DnsRemoteRule) { + rulesAdapter?.updateRemoteRules(rules) + } + + override fun onUpdateLocalRules(rules: DnsRuleRecycleItem.DnsLocalRule) { + rulesAdapter?.updateLocalRules(rules) + } + + override fun onUpdateTotalRules(count: Int) { + _binding?.let { + viewModel.totalRulesCount.set(count) + updateTotalRules(count) + } + } + + override fun onUpdateFinished() { + val ruleType = ruleType ?: return + viewModel.requestRules(ruleType) + } + + companion object { + const val RULE_TYPE_ARG = "pan.alexander.tordnscrypt.RULE_TYPE_ARG" + } + +} diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/DnsRulesViewModel.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/DnsRulesViewModel.kt new file mode 100644 index 000000000..8ce123eb6 --- /dev/null +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/DnsRulesViewModel.kt @@ -0,0 +1,256 @@ +/* + This file is part of InviZible Pro. + + InviZible Pro is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + InviZible Pro is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with InviZible Pro. If not, see . + + Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com + */ + +package pan.alexander.tordnscrypt.settings.dnscrypt_rules + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.plus +import pan.alexander.tordnscrypt.di.CoroutinesModule.Companion.SUPERVISOR_JOB_IO_DISPATCHER_SCOPE +import pan.alexander.tordnscrypt.domain.dns_rules.DnsRulesInteractor +import pan.alexander.tordnscrypt.domain.dns_rules.DnsRuleType +import pan.alexander.tordnscrypt.domain.preferences.PreferenceRepository +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.existing.RemixExistingRulesWorkManager +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.UpdateLocalRulesWorkManager +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.recycler.DnsRuleRecycleItem +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteRulesWorkManager +import pan.alexander.tordnscrypt.utils.logger.Logger.loge +import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REMOTE_BLACKLIST_URL +import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REMOTE_CLOAKING_URL +import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REMOTE_FORWARDING_URL +import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REMOTE_IP_BLACKLIST_URL +import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REMOTE_WHITELIST_URL +import java.util.concurrent.atomic.AtomicInteger +import javax.inject.Inject +import javax.inject.Named + +class DnsRulesViewModel @Inject constructor( + private val interactor: DnsRulesInteractor, + @Named(SUPERVISOR_JOB_IO_DISPATCHER_SCOPE) + private val baseCoroutineScope: CoroutineScope, + private val preferences: dagger.Lazy, + private val remixExistingRulesWorkManager: dagger.Lazy, + private val updateRemoteDnsRulesManager: dagger.Lazy, + private val updateLocalRulesWorkManager: dagger.Lazy +) : ViewModel() { + private val supervisorScope = baseCoroutineScope + CoroutineName("DnsRulesViewModel") + + private val dnsRulesMutableStateFlow: MutableStateFlow?> = + MutableStateFlow(null) + val dnsRulesStateFlow: StateFlow?> get() = dnsRulesMutableStateFlow + + val totalRulesCount = AtomicInteger(0) + + fun requestRules(rulesType: DnsRuleType) { + viewModelScope.launch { + try { + tryRequestRules(rulesType) + } catch (e: Exception) { + loge("DnsRulesViewModel requestRules", e) + } + } + + } + + private suspend fun tryRequestRules(rulesType: DnsRuleType) { + val totalRulesCount = interactor.getMixedRulesMetadata(rulesType).count + + val singleRules = interactor.getSingleRules(rulesType) + + this@DnsRulesViewModel.totalRulesCount.set(totalRulesCount) + + dnsRulesMutableStateFlow.value = mutableListOf().also { + + var displayedRemoteRulesLastIndex = -1 + val remoteRulesMetadata = interactor.getRemoteRulesMetadata(rulesType) + if (remoteRulesMetadata.count > 0) { + displayedRemoteRulesLastIndex = 0 + it.add( + displayedRemoteRulesLastIndex, + DnsRuleRecycleItem.DnsRemoteRule( + name = remoteRulesMetadata.name, + url = remoteRulesMetadata.url, + date = remoteRulesMetadata.date, + count = remoteRulesMetadata.count, + size = remoteRulesMetadata.size, + inProgress = false + ) + ) + } + val displayedAddButtonRemoteRulesLastIndex = displayedRemoteRulesLastIndex + 1 + it.add( + displayedAddButtonRemoteRulesLastIndex, + DnsRuleRecycleItem.AddRemoteRulesButton + ) + + val displayedAddButtonLocalRulesLastIndex: Int + val localRulesMetadata = interactor.getLocalRulesMetadata(rulesType) + if (localRulesMetadata.count > 0) { + val displayedLocalRulesLastIndex = displayedAddButtonRemoteRulesLastIndex + 1 + it.add( + displayedLocalRulesLastIndex, + DnsRuleRecycleItem.DnsLocalRule( + name = localRulesMetadata.name, + date = localRulesMetadata.date, + count = localRulesMetadata.count, + size = localRulesMetadata.size, + inProgress = false + ) + ) + displayedAddButtonLocalRulesLastIndex = displayedLocalRulesLastIndex + 1 + it.add( + displayedAddButtonLocalRulesLastIndex, + DnsRuleRecycleItem.AddLocalRulesButton + ) + } else { + displayedAddButtonLocalRulesLastIndex = displayedAddButtonRemoteRulesLastIndex + 1 + it.add( + displayedAddButtonLocalRulesLastIndex, + DnsRuleRecycleItem.AddLocalRulesButton + ) + } + + + it.addAll(singleRules) + + it.add(DnsRuleRecycleItem.AddSingleRuleButton) + } + } + + fun updateRemoteRules( + ruleType: DnsRuleType, + currentRules: List, + name: String + ) { + supervisorScope.launch { + try { + saveTemporarily(currentRules) + saveSingleRules(ruleType, getSingleRules(currentRules)) + updateRemoteDnsRulesManager.get().startRefreshDnsRules(name, ruleType) + } catch (e: Exception) { + loge("DnsRulesViewModel updateRemoteRules", e) + } + } + } + + fun deleteRemoteRules(ruleType: DnsRuleType, currentRules: List) { + supervisorScope.launch { + try { + saveTemporarily(currentRules) + updateRemoteDnsRulesManager.get().stopRefreshDnsRules(ruleType) + saveSingleRules(ruleType, getSingleRules(currentRules)) + delay(500) + interactor.clearRemoteRules(ruleType) + remixExistingRulesWorkManager.get().startMix(ruleType) + } catch (e: Exception) { + loge("DnsRulesViewModel deleteRemoteRules", e) + } + } + } + + fun importLocalRules( + ruleType: DnsRuleType, + currentRules: List, + files: Array<*> + ) { + supervisorScope.launch { + try { + saveTemporarily(currentRules) + saveSingleRules(ruleType, getSingleRules(currentRules)) + updateLocalRulesWorkManager.get().startImportDnsRules(ruleType, files) + } catch (e: Exception) { + loge("DnsRulesViewModel importLocalRules", e) + } + } + } + + fun deleteLocalRules(ruleType: DnsRuleType, currentRules: List) { + supervisorScope.launch { + try { + saveTemporarily(currentRules) + updateLocalRulesWorkManager.get().stopImportDnsRules(ruleType) + saveSingleRules(ruleType, getSingleRules(currentRules)) + delay(500) + interactor.clearLocalRules(ruleType) + remixExistingRulesWorkManager.get().startMix(ruleType) + } catch (e: Exception) { + loge("DnsRulesViewModel deleteLocalRules", e) + } + } + } + + private fun getSingleRules(rules: List) = + rules.filterIsInstance() + + fun saveTemporarily(rules: List) { + val savedRules = dnsRulesMutableStateFlow.value + if (savedRules == null || rules.size != savedRules.size || !rules.containsAll(savedRules)) { + dnsRulesMutableStateFlow.value = rules + } + } + + fun saveRules(ruleType: DnsRuleType, rules: List) { + supervisorScope.launch { + try { + val saved = saveSingleRules(ruleType, getSingleRules(rules)) + if (saved) { + remixExistingRulesWorkManager.get().startMix(ruleType) + } + } catch (e: Exception) { + loge("DnsRulesViewModel saveRules", e) + } + } + } + + fun saveRemoteRulesUrl(ruleType: DnsRuleType, url: String) = with(preferences.get()) { + val urlToSave = if (url.startsWith("http")) { + url + } else { + "https://$url" + } + when (ruleType) { + DnsRuleType.BLACKLIST -> setStringPreference(REMOTE_BLACKLIST_URL, urlToSave) + DnsRuleType.WHITELIST -> setStringPreference(REMOTE_WHITELIST_URL, urlToSave) + DnsRuleType.IP_BLACKLIST -> setStringPreference(REMOTE_IP_BLACKLIST_URL, urlToSave) + DnsRuleType.FORWARDING -> setStringPreference(REMOTE_FORWARDING_URL, urlToSave) + DnsRuleType.CLOAKING -> setStringPreference(REMOTE_CLOAKING_URL, urlToSave) + } + } + + private suspend fun saveSingleRules( + ruleType: DnsRuleType, + rules: List, + ): Boolean { + val singleRules = interactor.getSingleRules(ruleType) + if (singleRules.size != rules.size || !singleRules.containsAll(rules)) { + interactor.saveSingleRules(ruleType, rules) + return true + } + return false + } + + suspend fun isExternalStorageAllowsDirectAccess() = + interactor.isExternalStorageAllowsDirectAccess() +} diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/existing/RemixExistingDnsRulesWorker.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/existing/RemixExistingDnsRulesWorker.kt new file mode 100644 index 000000000..08bbea506 --- /dev/null +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/existing/RemixExistingDnsRulesWorker.kt @@ -0,0 +1,122 @@ +/* + This file is part of InviZible Pro. + + InviZible Pro is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + InviZible Pro is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with InviZible Pro. If not, see . + + Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com + */ + +package pan.alexander.tordnscrypt.settings.dnscrypt_rules.existing + +import android.content.Context +import androidx.work.CoroutineWorker +import androidx.work.WorkInfo +import androidx.work.WorkManager +import androidx.work.WorkerParameters +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.delay +import kotlinx.coroutines.runInterruptible +import pan.alexander.tordnscrypt.App +import pan.alexander.tordnscrypt.di.CoroutinesModule +import pan.alexander.tordnscrypt.domain.dns_rules.DnsRuleType +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.ImportRulesManager +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.UpdateLocalRulesWorkManager.Companion.REFRESH_LOCAL_DNS_BLACKLIST_WORK +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.UpdateLocalRulesWorkManager.Companion.REFRESH_LOCAL_DNS_CLOAKING_WORK +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.UpdateLocalRulesWorkManager.Companion.REFRESH_LOCAL_DNS_FORWARDING_WORK +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.UpdateLocalRulesWorkManager.Companion.REFRESH_LOCAL_DNS_IP_BLACKLIST_WORK +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.UpdateLocalRulesWorkManager.Companion.REFRESH_LOCAL_DNS_WHITELIST_WORK +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteRulesWorkManager.Companion.REFRESH_REMOTE_DNS_BLACKLIST_WORK +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteRulesWorkManager.Companion.REFRESH_REMOTE_DNS_CLOAKING_WORK +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteRulesWorkManager.Companion.REFRESH_REMOTE_DNS_FORWARDING_WORK +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteRulesWorkManager.Companion.REFRESH_REMOTE_DNS_IP_BLACKLIST_WORK +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteRulesWorkManager.Companion.REFRESH_REMOTE_DNS_WHITELIST_WORK +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.existing.RemixExistingRulesWorkManager.Companion.MIX_RULES_TYPE_ARG +import pan.alexander.tordnscrypt.utils.logger.Logger.loge +import javax.inject.Inject +import javax.inject.Named + +class RemixExistingDnsRulesWorker(private val appContext: Context, workerParams: WorkerParameters) : + CoroutineWorker(appContext, workerParams) { + + init { + App.instance.daggerComponent.inject(this) + } + + @Inject + @Named(CoroutinesModule.DISPATCHER_IO) + lateinit var dispatcherIo: CoroutineDispatcher + + override suspend fun doWork(): Result { + try { + val ruleType = inputData.getString(MIX_RULES_TYPE_ARG)?.let { + DnsRuleType.valueOf(it) + } ?: return Result.failure() + + while (isRemoteDnsRulesImportingInProgress(ruleType) + || isLocalDnsRulesImportingInProgress(ruleType) + ) { + delay(500) + } + + updateRules(ruleType) + + return Result.success() + } catch (e: Exception) { + loge("UpdateSingleDnsRulesWorker doWork", e) + } + return Result.failure() + } + + private suspend fun updateRules( + ruleType: DnsRuleType, + ) = runInterruptible(dispatcherIo) { + ImportRulesManager( + context = appContext, + rulesVariant = ruleType, + importType = ImportRulesManager.ImportType.SINGLE_RULES, + filePathToImport = emptyArray() + ).run() + } + + private fun isRemoteDnsRulesImportingInProgress(type: DnsRuleType): Boolean = + WorkManager.getInstance(appContext) + .getWorkInfosForUniqueWork(getRemoteWorkName(type)).get() + .firstOrNull()?.state == WorkInfo.State.RUNNING + + + private fun getRemoteWorkName(type: DnsRuleType) = + when (type) { + DnsRuleType.BLACKLIST -> REFRESH_REMOTE_DNS_BLACKLIST_WORK + DnsRuleType.WHITELIST -> REFRESH_REMOTE_DNS_WHITELIST_WORK + DnsRuleType.IP_BLACKLIST -> REFRESH_REMOTE_DNS_IP_BLACKLIST_WORK + DnsRuleType.FORWARDING -> REFRESH_REMOTE_DNS_FORWARDING_WORK + DnsRuleType.CLOAKING -> REFRESH_REMOTE_DNS_CLOAKING_WORK + } + + private fun isLocalDnsRulesImportingInProgress(type: DnsRuleType): Boolean = + WorkManager.getInstance(appContext) + .getWorkInfosForUniqueWork(getLocalWorkName(type)).get() + .firstOrNull()?.state == WorkInfo.State.RUNNING + + + private fun getLocalWorkName(type: DnsRuleType) = + when (type) { + DnsRuleType.BLACKLIST -> REFRESH_LOCAL_DNS_BLACKLIST_WORK + DnsRuleType.WHITELIST -> REFRESH_LOCAL_DNS_WHITELIST_WORK + DnsRuleType.IP_BLACKLIST -> REFRESH_LOCAL_DNS_IP_BLACKLIST_WORK + DnsRuleType.FORWARDING -> REFRESH_LOCAL_DNS_FORWARDING_WORK + DnsRuleType.CLOAKING -> REFRESH_LOCAL_DNS_CLOAKING_WORK + } + +} diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/existing/RemixExistingRulesWorkManager.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/existing/RemixExistingRulesWorkManager.kt new file mode 100644 index 000000000..4ef6f3afb --- /dev/null +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/existing/RemixExistingRulesWorkManager.kt @@ -0,0 +1,94 @@ +/* + This file is part of InviZible Pro. + + InviZible Pro is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + InviZible Pro is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with InviZible Pro. If not, see . + + Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com + */ + +package pan.alexander.tordnscrypt.settings.dnscrypt_rules.existing + +import android.content.Context +import androidx.work.BackoffPolicy +import androidx.work.Constraints +import androidx.work.ExistingWorkPolicy +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkManager +import androidx.work.WorkRequest +import androidx.work.workDataOf +import pan.alexander.tordnscrypt.domain.dns_rules.DnsRuleType +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +class RemixExistingRulesWorkManager @Inject constructor( + private val context: Context, +) { + + fun startMix(ruleType: DnsRuleType) { + + val constraints = Constraints.Builder() + .setRequiresStorageNotLow(true) + .build() + + val mixRequest = OneTimeWorkRequestBuilder() + .setConstraints(constraints) + .setBackoffCriteria( + BackoffPolicy.EXPONENTIAL, + WorkRequest.DEFAULT_BACKOFF_DELAY_MILLIS, + TimeUnit.MILLISECONDS + ) + .setInputData( + workDataOf( + MIX_RULES_TYPE_ARG to ruleType.name, + ) + ) + .build() + + WorkManager.getInstance(context) + .enqueueUniqueWork( + getWorkName(ruleType), + ExistingWorkPolicy.REPLACE, + mixRequest + ) + } + + fun stopMix(type: DnsRuleType) { + WorkManager.getInstance(context) + .cancelUniqueWork(getWorkName(type)) + } + + private fun getWorkName(type: DnsRuleType) = + when (type) { + DnsRuleType.BLACKLIST -> MIX_DNS_BLACKLIST_WORK + DnsRuleType.WHITELIST -> MIX_DNS_WHITELIST_WORK + DnsRuleType.IP_BLACKLIST -> MIX_DNS_IP_BLACKLIST_WORK + DnsRuleType.FORWARDING -> MIX_DNS_FORWARDING_WORK + DnsRuleType.CLOAKING -> MIX_DNS_CLOAKING_WORK + } + + companion object { + const val MIX_RULES_TYPE_ARG = "pan.alexander.tordnscrypt.SINGLE_RULES_TYPE_ARG" + + const val MIX_DNS_BLACKLIST_WORK = + "pan.alexander.tordnscrypt.MIX_DNS_BLACKLIST_WORK" + const val MIX_DNS_WHITELIST_WORK = + "pan.alexander.tordnscrypt.MIX_DNS_WHITELIST_WORK" + const val MIX_DNS_IP_BLACKLIST_WORK = + "pan.alexander.tordnscrypt.MIX_DNS_IP_BLACKLIST_WORK" + const val MIX_DNS_FORWARDING_WORK = + "pan.alexander.tordnscrypt.MIX_DNS_FORWARDING_WORK" + const val MIX_DNS_CLOAKING_WORK = + "pan.alexander.tordnscrypt.REFRESH_SINGLE_DNS_CLOAKING_WORK" + } +} diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/local/DnsRulesUpdateProgress.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/local/DnsRulesUpdateProgress.kt new file mode 100644 index 000000000..af306e92b --- /dev/null +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/local/DnsRulesUpdateProgress.kt @@ -0,0 +1,47 @@ +/* + This file is part of InviZible Pro. + + InviZible Pro is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + InviZible Pro is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with InviZible Pro. If not, see . + + Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com + */ + +package pan.alexander.tordnscrypt.settings.dnscrypt_rules.local + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +sealed class DnsRulesUpdateProgress : Parcelable { + + data class UpdateProgress( + val name: String, + val url: String? = null, + val size: Long, + val count: Int + ): DnsRulesUpdateProgress() + + data class UpdateFinished( + val name: String, + val url: String? = null, + val size: Long, + val count: Int + ): DnsRulesUpdateProgress() + + data class UpdateFailure( + val name: String, + val url: String? = null, + val error: String + ): DnsRulesUpdateProgress() +} diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/local/ImportRulesManager.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/local/ImportRulesManager.kt new file mode 100644 index 000000000..bb7663107 --- /dev/null +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/local/ImportRulesManager.kt @@ -0,0 +1,726 @@ +/* + This file is part of InviZible Pro. + + InviZible Pro is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + InviZible Pro is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with InviZible Pro. If not, see . + + Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com + */ + +package pan.alexander.tordnscrypt.settings.dnscrypt_rules.local + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.provider.OpenableColumns +import androidx.localbroadcastmanager.content.LocalBroadcastManager +import pan.alexander.tordnscrypt.App +import pan.alexander.tordnscrypt.modules.ModulesRestarter +import pan.alexander.tordnscrypt.modules.ModulesStatus +import pan.alexander.tordnscrypt.settings.PathVars +import pan.alexander.tordnscrypt.domain.dns_rules.DnsRuleType +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.ImportRulesManager.DnsRulesRegex.Companion.blackListHostRulesRegex +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.ImportRulesManager.DnsRulesRegex.Companion.blacklistIPRulesRegex +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.ImportRulesManager.DnsRulesRegex.Companion.cloakingRulesRegex +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.ImportRulesManager.DnsRulesRegex.Companion.forwardingRulesRegex +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.ImportRulesManager.DnsRulesRegex.Companion.hostFileRegex +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.ImportRulesManager.DnsRulesRegex.Companion.whiteListHostRulesRegex +import pan.alexander.tordnscrypt.utils.Constants.META_ADDRESS +import pan.alexander.tordnscrypt.utils.enums.ModuleState +import pan.alexander.tordnscrypt.utils.logger.Logger.loge +import pan.alexander.tordnscrypt.utils.wakelock.WakeLocksManager +import java.io.BufferedReader +import java.io.File +import java.io.InputStreamReader +import java.io.PrintWriter +import java.util.concurrent.locks.ReentrantLock + +private val excludeFromHost = listOf("localhost", "localhost.localdomain", "local", META_ADDRESS) +private val reentrantLock = ReentrantLock() +private val wakeLocksManager = WakeLocksManager.getInstance() + +class ImportRulesManager( + private val context: Context, + private var rulesVariant: DnsRuleType, + private var remoteRulesUrl: String? = null, + private var remoteRulesName: String? = null, + private val importType: ImportType, + private val filePathToImport: Array<*> +) : Runnable { + + private val localBroadcastManager by lazy { + LocalBroadcastManager.getInstance(context) + } + + private val pathVars: PathVars = App.instance.daggerComponent.getPathVars().get() + + private val blackListHostRulesPath = pathVars.dnsCryptBlackListPath + private val blackListHostSingleRulesPath = pathVars.dnsCryptSingleBlackListPath + private val blackListHostRulesLocalPath = pathVars.dnsCryptLocalBlackListPath + private val blackListHostRulesRemotePath = pathVars.dnsCryptRemoteBlackListPath + + private val blackListIPRulesPath = pathVars.dnsCryptIPBlackListPath + private val blackListIPRulesLocalPath = pathVars.dnsCryptLocalIPBlackListPath + private val blackListSingleIPRulesPath = pathVars.dnsCryptSingleIPBlackListPath + private val blackListIPRulesRemotePath = pathVars.dnsCryptRemoteIPBlackListPath + + private val whiteListHostRulesPath = pathVars.dnsCryptWhiteListPath + private val whiteListSingleRulesPath = pathVars.dnsCryptSingleWhiteListPath + private val whiteListHostRulesLocalPath = pathVars.dnsCryptLocalWhiteListPath + private val whiteListHostRulesRemotePath = pathVars.dnsCryptRemoteWhiteListPath + + private val cloakingRulesPath = pathVars.dnsCryptCloakingRulesPath + private val cloakingSingleRulesPath = pathVars.dnsCryptSingleCloakingRulesPath + private val cloakingRulesLocalPath = pathVars.dnsCryptLocalCloakingRulesPath + private val cloakingRulesRemotePath = pathVars.dnsCryptRemoteCloakingRulesPath + + private val forwardingRulesPath = pathVars.dnsCryptForwardingRulesPath + private val forwardingSingleRulesPath = pathVars.dnsCryptSingleForwardingRulesPath + private val forwardingRulesLocalPath = pathVars.dnsCryptLocalForwardingRulesPath + private val forwardingRulesRemotePath = pathVars.dnsCryptRemoteForwardingRulesPath + + private val defaultForwardingRule = pathVars.dnsCryptDefaultForwardingRule + private val defaultCloakingRule = pathVars.dnsCryptDefaultCloakingRule + + private val contentResolver = context.applicationContext.contentResolver + + private var hashes = hashSetOf() + private var savedTime = System.currentTimeMillis() + + private var powerLocked = false + + private var rulesFilePath: String = "" + private var singleRulesFilePath: String = "" + private var localRulesFilePath: String = "" + private var remoteRulesFilePath: String = "" + private var rulesRegex: Regex = blackListHostRulesRegex + + @Volatile + private var importedLinesCount = 0 + + @Volatile + private var totalLinesCount = 0 + private var fileNames = "" + + + override fun run() { + + when (rulesVariant) { + DnsRuleType.BLACKLIST -> { + rulesFilePath = blackListHostRulesPath + singleRulesFilePath = blackListHostSingleRulesPath + localRulesFilePath = blackListHostRulesLocalPath + remoteRulesFilePath = blackListHostRulesRemotePath + rulesRegex = blackListHostRulesRegex + } + + DnsRuleType.WHITELIST -> { + rulesFilePath = whiteListHostRulesPath + singleRulesFilePath = whiteListSingleRulesPath + localRulesFilePath = whiteListHostRulesLocalPath + remoteRulesFilePath = whiteListHostRulesRemotePath + rulesRegex = whiteListHostRulesRegex + } + + DnsRuleType.IP_BLACKLIST -> { + rulesFilePath = blackListIPRulesPath + singleRulesFilePath = blackListSingleIPRulesPath + localRulesFilePath = blackListIPRulesLocalPath + remoteRulesFilePath = blackListIPRulesRemotePath + rulesRegex = blacklistIPRulesRegex + } + + DnsRuleType.CLOAKING -> { + rulesFilePath = cloakingRulesPath + singleRulesFilePath = cloakingSingleRulesPath + localRulesFilePath = cloakingRulesLocalPath + remoteRulesFilePath = cloakingRulesRemotePath + rulesRegex = cloakingRulesRegex + } + + DnsRuleType.FORWARDING -> { + rulesFilePath = forwardingRulesPath + singleRulesFilePath = forwardingSingleRulesPath + localRulesFilePath = forwardingRulesLocalPath + remoteRulesFilePath = forwardingRulesRemotePath + rulesRegex = forwardingRulesRegex + } + } + + doTheJob(filePathToImport) + } + + private fun doTheJob( + filesToImport: Array<*> + ) { + + reentrantLock.lock() + + if (!wakeLocksManager.isPowerWakeLockHeld) { + wakeLocksManager.managePowerWakelock(context, true) + powerLocked = true + } + + try { + + val fileToSave = when (importType) { + ImportType.LOCAL_RULES, ImportType.SINGLE_RULES -> localRulesFilePath + ImportType.REMOTE_RULES -> remoteRulesFilePath + } + + if (filesToImport.isNotEmpty()) { + File(fileToSave).printWriter().use { + filesToImport.map { path -> + when (path) { + is String -> getFileNameFromPath(path) + is Uri -> getFileNameFromUri(path) + else -> throw IllegalArgumentException("ImportRulesManager unknown path type") + } + }.let { names -> + fileNames = names.joinToString() + when (importType) { + ImportType.LOCAL_RULES -> addFileHeader(it, names) + + ImportType.REMOTE_RULES -> remoteRulesUrl?.let { url -> + addFileHeader(it, listOf(url)) + } + + ImportType.SINGLE_RULES -> Unit + } + } + mixFiles(it, filesToImport.toMutableList(), true) + } + } + + val fileToAdd = when (importType) { + ImportType.LOCAL_RULES, ImportType.SINGLE_RULES -> remoteRulesFilePath + ImportType.REMOTE_RULES -> localRulesFilePath + } + + val filesForFinalMixing = mutableListOf( + singleRulesFilePath, fileToSave, fileToAdd + ) + + File(rulesFilePath).printWriter().use { + //addDefaultLinesIfRequired() + mixFiles(it, filesForFinalMixing, false) + } + + if (importedLinesCount > 0) { + sendImportFinishedBroadcast( + getRulesFileSize(), + importedLinesCount + ) + } else { + sendImportFailedBroadcast( + "Imported zero rules" + ) + } + + sendTotalRulesBroadcast(totalLinesCount) + + } catch (e: Exception) { + sendImportFailedBroadcast( + e.message ?: "" + ) + loge("ImportRules doTheJob", e) + } finally { + + if (powerLocked) { + wakeLocksManager.stopPowerWakelock() + } + + restartDNSCryptIfRequired() + + reentrantLock.unlock() + } + + } + + private fun mixFiles( + printWriter: PrintWriter, + filesToImport: MutableList, + countAsImportedLines: Boolean + ) { + try { + + hashes.clear() + + filesToImport.forEachIndexed { index, file -> + + if (file is String) { + mixFilesWithPass( + index, + file, + filesToImport.size, + rulesRegex, + printWriter, + countAsImportedLines + ) + } else if (file is Uri) { + mixFilesWithUri( + index, + file, + filesToImport.size, + rulesRegex, + printWriter, + countAsImportedLines + ) + } + } + + sendImportProgressBroadcast( + getRulesFileSize(), + importedLinesCount + ) + sendTotalRulesBroadcast(totalLinesCount) + } catch (e: Exception) { + loge("ImportRules mixFiles", e) + } + } + + private fun mixFilesWithPass( + index: Int, + file: String, + filesCount: Int, + rulesRegex: Regex, + printWriter: PrintWriter, + countAsImportedLines: Boolean + ) { + + if (file.isNotEmpty()) { + val inputFile = File(file) + + if (inputFile.isFile) { + try { + val blackListFileIsHost = if (DnsRuleType.BLACKLIST == rulesVariant) { + isInputFileFormatCorrect(inputFile, hostFileRegex) + } else { + false + } + + val hashesNew = hashSetOf() + + if (blackListFileIsHost || isInputFileFormatCorrect(inputFile, rulesRegex)) { + inputFile.bufferedReader().use { reader -> + mixFilesCommonPart( + printWriter, + reader, + rulesRegex, + index, + filesCount, + hashesNew, + blackListFileIsHost, + countAsImportedLines + ) + } + } + } catch (e: Exception) { + loge("ImportRules mixFilesWithPass", e) + } + } + } + } + + private fun mixFilesWithUri( + index: Int, + uri: Uri, + filesCount: Int, + rulesRegex: Regex, + printWriter: PrintWriter, + countAsImportedLines: Boolean + ) { + try { + val blackListFileIsHost = if (DnsRuleType.BLACKLIST == rulesVariant) { + isInputFileFormatCorrect(uri, hostFileRegex) + } else { + false + } + + val hashesNew = hashSetOf() + + if (blackListFileIsHost || isInputFileFormatCorrect(uri, rulesRegex)) { + contentResolver.openInputStream(uri)?.use { inputStream -> + BufferedReader(InputStreamReader(inputStream)).use { reader -> + mixFilesCommonPart( + printWriter, + reader, + rulesRegex, + index, + filesCount, + hashesNew, + blackListFileIsHost, + countAsImportedLines + ) + } + } + } + } catch (e: Exception) { + loge("ImportRules mixFilesWithUri", e) + } + } + + private fun mixFilesCommonPart( + printWriter: PrintWriter, + reader: BufferedReader, + rulesRegex: Regex, + index: Int, + filesCount: Int, + hashesNew: MutableSet, + blackListFileIsHost: Boolean, + countAsImportedLines: Boolean + ) { + var line = reader.readLine()?.trim() + while (line != null && !Thread.currentThread().isInterrupted) { + val lineReady = if (blackListFileIsHost) { + hostToBlackList(line) + } else { + cleanRule(line, rulesRegex) + } + + if (lineReady.isNotEmpty() && (index == 0 || !hashes.contains(lineReady))) { + + if (filesCount > 1 && index < filesCount - 1) { + hashesNew += lineReady + } + + printWriter.println(lineReady) + if (countAsImportedLines) { + importedLinesCount++ + } else { + totalLinesCount++ + } + val currentTime = System.currentTimeMillis() + if (currentTime - savedTime > 300) { + if (countAsImportedLines) { + sendImportProgressBroadcast( + getRulesFileSize(), + importedLinesCount + ) + } else { + sendTotalRulesBroadcast(totalLinesCount) + } + savedTime = currentTime + } + } + line = reader.readLine()?.trim() + } + + hashes += hashesNew + + if (countAsImportedLines) { + sendImportProgressBroadcast( + getRulesFileSize(), + importedLinesCount + ) + } else { + sendTotalRulesBroadcast(totalLinesCount) + } + } + + private fun cleanRule(line: String, regExp: Regex): String { + + if (line.startsWith("#") || !line.matches(regExp)) { + return "" + } + + return line + } + + private fun hostToBlackList(line: String): String { + var output = "" + + if (line.startsWith("#") || !line.matches(hostFileRegex)) { + return "" + } + + val index = line.lastIndexOf(" ") + 1 + if (index in 8 until line.length) { + output = line.substring(index) + } + + if (excludeFromHost.contains(output)) { + return "" + } + + return output + } + + private fun addDefaultLinesIfRequired() { + if (DnsRuleType.CLOAKING == rulesVariant) { + val file = File(singleRulesFilePath) + if (!file.isFile) { + file.createNewFile() + } + if (file.length() == 0L) { + file.printWriter().use { + it.println(defaultCloakingRule) + } + } + } else if (DnsRuleType.FORWARDING == rulesVariant) { + val file = File(singleRulesFilePath) + if (!file.isFile) { + file.createNewFile() + } + if (file.length() == 0L) { + file.printWriter().use { + it.println(defaultForwardingRule) + } + } + } + } + + private fun isInputFileFormatCorrect(file: File, regExp: Regex): Boolean { + file.bufferedReader().use { + try { + return isInputFileFormatCorrect(it, regExp) + } catch (e: Exception) { + loge("ImportRules isInputFileFormatCorrect", e) + } + } + return false + } + + private fun isInputFileFormatCorrect(uri: Uri, regExp: Regex): Boolean { + contentResolver.openInputStream(uri)?.use { inputStream -> + try { + BufferedReader(InputStreamReader(inputStream)).use { reader -> + return isInputFileFormatCorrect(reader, regExp) + } + } catch (e: Exception) { + loge("ImportRules isInputFileFormatCorrect", e) + } + } + return false + } + + private fun isInputFileFormatCorrect(reader: BufferedReader, regExp: Regex): Boolean { + var line: String? = reader.readLine()?.trim() + var index = 0 + while (line != null) { + + if (Thread.currentThread().isInterrupted) { + return false + } + + if (line.isNotEmpty() && !line.contains("#") && !line.contains("!")) { + index++ + if (line.matches(regExp)) { + return true + } else if (index > 100) { + return false + } + } + + line = reader.readLine()?.trim() + } + return false + } + + private fun restartDNSCryptIfRequired() { + if (ModulesStatus.getInstance().dnsCryptState == ModuleState.RUNNING) { + ModulesRestarter.restartDNSCrypt(context) + } + } + + private fun getFileNameFromUri(uri: Uri): String { + val cursor = context.contentResolver.query(uri, null, null, null, null) + cursor?.moveToFirst() + val nameColumnIndex = cursor?.getColumnIndex(OpenableColumns.DISPLAY_NAME) ?: -1 + val fileName = if (nameColumnIndex >= 0) { + cursor?.getString(nameColumnIndex) ?: "" + } else { + "" + } + cursor?.close() + return fileName + } + + private fun getFileNameFromPath(path: String) = + path.substringAfterLast("/") + + private fun getRulesFileSize(): Long { + val filePath = when (importType) { + ImportType.LOCAL_RULES -> localRulesFilePath + ImportType.REMOTE_RULES -> remoteRulesFilePath + ImportType.SINGLE_RULES -> singleRulesFilePath + } + return File(filePath).length() + } + + private fun addFileHeader( + printWriter: PrintWriter, + names: List + ) = with(printWriter) { + val nameLine = "# ${names.joinToString().replace("#", "")} #" + println("#".repeat(nameLine.length)) + println() + println(nameLine) + println() + println("#".repeat(nameLine.length)) + println() + } + + private fun sendImportProgressBroadcast( + size: Long, + count: Int + ) { + when (importType) { + ImportType.LOCAL_RULES -> { + Intent(UPDATE_LOCAL_DNS_RULES_PROGRESS_ACTION).apply { + putExtra( + UPDATE_DNS_RULES_PROGRESS_DATA, + DnsRulesUpdateProgress.UpdateProgress( + name = fileNames, + size = size, + count = count + ) + ) + } + } + + ImportType.REMOTE_RULES -> { + Intent(UPDATE_REMOTE_DNS_RULES_PROGRESS_ACTION).apply { + putExtra( + UPDATE_DNS_RULES_PROGRESS_DATA, + DnsRulesUpdateProgress.UpdateProgress( + name = remoteRulesName ?: "", + url = remoteRulesUrl, + size = size, + count = count + ) + ) + } + } + + ImportType.SINGLE_RULES -> null + }?.let { + localBroadcastManager.sendBroadcast(it) + } + } + + private fun sendImportFinishedBroadcast( + size: Long, + count: Int + ) { + when (importType) { + ImportType.LOCAL_RULES -> { + Intent(UPDATE_LOCAL_DNS_RULES_PROGRESS_ACTION).apply { + putExtra( + UPDATE_DNS_RULES_PROGRESS_DATA, + DnsRulesUpdateProgress.UpdateFinished( + name = fileNames, + size = size, + count = count + ) + ) + } + } + + ImportType.REMOTE_RULES -> { + Intent(UPDATE_REMOTE_DNS_RULES_PROGRESS_ACTION).apply { + putExtra( + UPDATE_DNS_RULES_PROGRESS_DATA, + DnsRulesUpdateProgress.UpdateFinished( + name = remoteRulesName ?: "", + url = remoteRulesUrl, + size = size, + count = count + ) + ) + } + } + + ImportType.SINGLE_RULES -> null + }?.let { + localBroadcastManager.sendBroadcast(it) + } + } + + private fun sendImportFailedBroadcast( + error: String + ) { + when (importType) { + ImportType.LOCAL_RULES -> { + Intent(UPDATE_LOCAL_DNS_RULES_PROGRESS_ACTION).apply { + putExtra( + UPDATE_DNS_RULES_PROGRESS_DATA, + DnsRulesUpdateProgress.UpdateFailure( + name = fileNames, + error = error + ) + ) + } + } + + ImportType.REMOTE_RULES -> { + Intent(UPDATE_REMOTE_DNS_RULES_PROGRESS_ACTION).apply { + putExtra( + UPDATE_DNS_RULES_PROGRESS_DATA, + DnsRulesUpdateProgress.UpdateFailure( + name = remoteRulesName ?: "", + url = remoteRulesUrl, + error = error + ) + ) + } + } + + ImportType.SINGLE_RULES -> null + }?.let { + localBroadcastManager.sendBroadcast(it) + } + } + + private fun sendTotalRulesBroadcast(count: Int) { + Intent(UPDATE_TOTAL_DNS_RULES_PROGRESS_ACTION).apply { + putExtra( + UPDATE_TOTAL_DNS_RULES_PROGRESS_DATA, + count + ) + }.also { + localBroadcastManager.sendBroadcast(it) + } + } + + enum class ImportType { + REMOTE_RULES, + LOCAL_RULES, + SINGLE_RULES + } + + interface DnsRulesRegex { + companion object { + val blackListHostRulesRegex = Regex("^[a-zA-Z\\d-.=_*\\[\\]?,]+$") + val blacklistIPRulesRegex = Regex("^[0-9a-fA-F:.=*\\[\\]]+$") + val cloakingRulesRegex = Regex("^[a-zA-Z\\d-.=_*\\[\\]?]+[ \\t]+[a-zA-Z\\d-.=_*:]+$") + val forwardingRulesRegex = + Regex("^[a-zA-Z\\d-._]+[ \\t]+[0-9a-fA-F:.,\\[\\]]+$") + val whiteListHostRulesRegex = Regex("^[a-zA-Z\\d-.=_*\\[\\]?]+$") + val hostFileRegex = Regex("^(?:0.0.0.0|127.0.0.1)[ \\t]+[a-zA-Z\\d-._]+$") + } + } + + companion object { + const val UPDATE_REMOTE_DNS_RULES_PROGRESS_ACTION = + "pan.alexander.tordnscrypt.UPDATE_REMOTE_DNS_RULES_PROGRESS_ACTION" + const val UPDATE_LOCAL_DNS_RULES_PROGRESS_ACTION = + "pan.alexander.tordnscrypt.UPDATE_LOCAL_DNS_RULES_PROGRESS_ACTION" + const val UPDATE_DNS_RULES_PROGRESS_DATA = + "pan.alexander.tordnscrypt.UPDATE_DNS_RULES_PROGRESS_DATA" + const val UPDATE_TOTAL_DNS_RULES_PROGRESS_ACTION = + "pan.alexander.tordnscrypt.UPDATE_TOTAL_DNS_RULES_PROGRESS_ACTION" + const val UPDATE_TOTAL_DNS_RULES_PROGRESS_DATA = + "pan.alexander.tordnscrypt.UPDATE_TOTAL_DNS_RULES_PROGRESS_DATA" + } +} diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/local/UpdateLocalDnsRulesWorker.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/local/UpdateLocalDnsRulesWorker.kt new file mode 100644 index 000000000..6ee0a1b31 --- /dev/null +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/local/UpdateLocalDnsRulesWorker.kt @@ -0,0 +1,128 @@ +/* + This file is part of InviZible Pro. + + InviZible Pro is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + InviZible Pro is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with InviZible Pro. If not, see . + + Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com + */ + +package pan.alexander.tordnscrypt.settings.dnscrypt_rules.local + +import android.content.Context +import android.net.Uri +import androidx.work.CoroutineWorker +import androidx.work.WorkInfo +import androidx.work.WorkManager +import androidx.work.WorkerParameters +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.delay +import kotlinx.coroutines.runInterruptible +import pan.alexander.tordnscrypt.App +import pan.alexander.tordnscrypt.di.CoroutinesModule +import pan.alexander.tordnscrypt.domain.dns_rules.DnsRuleType +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.UpdateLocalRulesWorkManager.Companion.LOCAL_RULES_PATH_ARG +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.UpdateLocalRulesWorkManager.Companion.LOCAL_RULES_TYPE_ARG +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.UpdateLocalRulesWorkManager.Companion.LOCAL_RULES_URI_ARG +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteRulesWorkManager.Companion.REFRESH_REMOTE_DNS_BLACKLIST_WORK +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteRulesWorkManager.Companion.REFRESH_REMOTE_DNS_CLOAKING_WORK +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteRulesWorkManager.Companion.REFRESH_REMOTE_DNS_FORWARDING_WORK +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteRulesWorkManager.Companion.REFRESH_REMOTE_DNS_IP_BLACKLIST_WORK +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteRulesWorkManager.Companion.REFRESH_REMOTE_DNS_WHITELIST_WORK +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.existing.RemixExistingRulesWorkManager.Companion.MIX_DNS_BLACKLIST_WORK +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.existing.RemixExistingRulesWorkManager.Companion.MIX_DNS_CLOAKING_WORK +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.existing.RemixExistingRulesWorkManager.Companion.MIX_DNS_FORWARDING_WORK +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.existing.RemixExistingRulesWorkManager.Companion.MIX_DNS_IP_BLACKLIST_WORK +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.existing.RemixExistingRulesWorkManager.Companion.MIX_DNS_WHITELIST_WORK +import pan.alexander.tordnscrypt.utils.logger.Logger.loge +import javax.inject.Inject +import javax.inject.Named + +class UpdateLocalDnsRulesWorker(private val appContext: Context, workerParams: WorkerParameters) : + CoroutineWorker(appContext, workerParams) { + + init { + App.instance.daggerComponent.inject(this) + } + + @Inject + @Named(CoroutinesModule.DISPATCHER_IO) + lateinit var dispatcherIo: CoroutineDispatcher + + override suspend fun doWork(): Result { + try { + val ruleType = inputData.getString(LOCAL_RULES_TYPE_ARG)?.let { + DnsRuleType.valueOf(it) + } ?: return Result.failure() + val filesWithPath = inputData.getStringArray(LOCAL_RULES_PATH_ARG) + val filesWithUri = inputData.getStringArray(LOCAL_RULES_URI_ARG) + val files = filesWithPath ?: (filesWithUri?.map { Uri.parse(it) }?.toTypedArray() + ?: return Result.failure()) + + while (isRemoteDnsRulesImportingInProgress(ruleType) + || isMixDnsRulesInProgress(ruleType) + ) { + delay(500) + } + + importRulesFromFiles(files, ruleType) + + return Result.success() + } catch (e: Exception) { + loge("UpdateLocalDnsRulesWorker doWork", e) + } + return Result.failure() + } + + private suspend fun importRulesFromFiles( + files: Array<*>, + ruleType: DnsRuleType, + ) = runInterruptible(dispatcherIo) { + ImportRulesManager( + context = appContext, + rulesVariant = ruleType, + importType = ImportRulesManager.ImportType.LOCAL_RULES, + filePathToImport = files + ).run() + } + + private fun isRemoteDnsRulesImportingInProgress(type: DnsRuleType): Boolean = + WorkManager.getInstance(appContext) + .getWorkInfosForUniqueWork(getRemoteWorkName(type)).get() + .firstOrNull()?.state == WorkInfo.State.RUNNING + + + private fun getRemoteWorkName(type: DnsRuleType) = + when (type) { + DnsRuleType.BLACKLIST -> REFRESH_REMOTE_DNS_BLACKLIST_WORK + DnsRuleType.WHITELIST -> REFRESH_REMOTE_DNS_WHITELIST_WORK + DnsRuleType.IP_BLACKLIST -> REFRESH_REMOTE_DNS_IP_BLACKLIST_WORK + DnsRuleType.FORWARDING -> REFRESH_REMOTE_DNS_FORWARDING_WORK + DnsRuleType.CLOAKING -> REFRESH_REMOTE_DNS_CLOAKING_WORK + } + + private fun isMixDnsRulesInProgress(type: DnsRuleType): Boolean = + WorkManager.getInstance(appContext) + .getWorkInfosForUniqueWork(getMixWorkName(type)).get() + .firstOrNull()?.state == WorkInfo.State.RUNNING + + private fun getMixWorkName(type: DnsRuleType) = + when (type) { + DnsRuleType.BLACKLIST -> MIX_DNS_BLACKLIST_WORK + DnsRuleType.WHITELIST -> MIX_DNS_WHITELIST_WORK + DnsRuleType.IP_BLACKLIST -> MIX_DNS_IP_BLACKLIST_WORK + DnsRuleType.FORWARDING -> MIX_DNS_FORWARDING_WORK + DnsRuleType.CLOAKING -> MIX_DNS_CLOAKING_WORK + } + +} diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/local/UpdateLocalRulesWorkManager.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/local/UpdateLocalRulesWorkManager.kt new file mode 100644 index 000000000..e13bbe000 --- /dev/null +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/local/UpdateLocalRulesWorkManager.kt @@ -0,0 +1,105 @@ +/* + This file is part of InviZible Pro. + + InviZible Pro is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + InviZible Pro is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with InviZible Pro. If not, see . + + Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com + */ + +package pan.alexander.tordnscrypt.settings.dnscrypt_rules.local + +import android.content.Context +import androidx.work.BackoffPolicy +import androidx.work.Constraints +import androidx.work.ExistingWorkPolicy +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkManager +import androidx.work.WorkRequest +import androidx.work.workDataOf +import pan.alexander.tordnscrypt.domain.dns_rules.DnsRuleType +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +class UpdateLocalRulesWorkManager @Inject constructor( + private val context: Context, +) { + + fun startImportDnsRules(ruleType: DnsRuleType, filesToImport: Array<*>) { + + filesToImport.firstOrNull() ?: return + + val constraints = Constraints.Builder() + .setRequiresStorageNotLow(true) + .build() + + val files = if (filesToImport.first() is String) { + LOCAL_RULES_PATH_ARG to filesToImport.map { it.toString() }.toTypedArray() + } else { + LOCAL_RULES_URI_ARG to filesToImport.map { it.toString() }.toTypedArray() + } + + val importRequest = OneTimeWorkRequestBuilder() + .setConstraints(constraints) + .setBackoffCriteria( + BackoffPolicy.EXPONENTIAL, + WorkRequest.DEFAULT_BACKOFF_DELAY_MILLIS, + TimeUnit.MILLISECONDS + ) + .setInputData( + workDataOf( + LOCAL_RULES_TYPE_ARG to ruleType.name, + files + ) + ) + .build() + + WorkManager.getInstance(context) + .enqueueUniqueWork( + getWorkName(ruleType), + ExistingWorkPolicy.REPLACE, + importRequest + ) + } + + fun stopImportDnsRules(type: DnsRuleType) { + WorkManager.getInstance(context) + .cancelUniqueWork(getWorkName(type)) + } + + private fun getWorkName(type: DnsRuleType) = + when (type) { + DnsRuleType.BLACKLIST -> REFRESH_LOCAL_DNS_BLACKLIST_WORK + DnsRuleType.WHITELIST -> REFRESH_LOCAL_DNS_WHITELIST_WORK + DnsRuleType.IP_BLACKLIST -> REFRESH_LOCAL_DNS_IP_BLACKLIST_WORK + DnsRuleType.FORWARDING -> REFRESH_LOCAL_DNS_FORWARDING_WORK + DnsRuleType.CLOAKING -> REFRESH_LOCAL_DNS_CLOAKING_WORK + } + + companion object { + const val LOCAL_RULES_TYPE_ARG = "pan.alexander.tordnscrypt.LOCAL_RULES_TYPE_ARG" + const val LOCAL_RULES_PATH_ARG = "pan.alexander.tordnscrypt.LOCAL_RULES_PATH_ARG" + const val LOCAL_RULES_URI_ARG = "pan.alexander.tordnscrypt.LOCAL_RULES_URI_ARG" + + const val REFRESH_LOCAL_DNS_BLACKLIST_WORK = + "pan.alexander.tordnscrypt.REFRESH_LOCAL_DNS_BLACKLIST_WORK" + const val REFRESH_LOCAL_DNS_WHITELIST_WORK = + "pan.alexander.tordnscrypt.REFRESH_LOCAL_DNS_WHITELIST_WORK" + const val REFRESH_LOCAL_DNS_IP_BLACKLIST_WORK = + "pan.alexander.tordnscrypt.REFRESH_LOCAL_DNS_IP_BLACKLIST_WORK" + const val REFRESH_LOCAL_DNS_FORWARDING_WORK = + "pan.alexander.tordnscrypt.REFRESH_LOCAL_DNS_FORWARDING_WORK" + const val REFRESH_LOCAL_DNS_CLOAKING_WORK = + "pan.alexander.tordnscrypt.REFRESH_LOCAL_DNS_CLOAKING_WORK" + } +} diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/receiver/DnsRulesReceiver.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/receiver/DnsRulesReceiver.kt new file mode 100644 index 000000000..ed35e87b1 --- /dev/null +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/receiver/DnsRulesReceiver.kt @@ -0,0 +1,278 @@ +/* + This file is part of InviZible Pro. + + InviZible Pro is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + InviZible Pro is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with InviZible Pro. If not, see . + + Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com + */ + +package pan.alexander.tordnscrypt.settings.dnscrypt_rules.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.Build +import androidx.localbroadcastmanager.content.LocalBroadcastManager +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.DownloadRemoteRulesManager.Companion.DOWNLOAD_REMOTE_DNS_RULES_PROGRESS_ACTION +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.DownloadRemoteRulesManager.Companion.DOWNLOAD_REMOTE_DNS_RULES_PROGRESS_DATA +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.ImportRulesManager.Companion.UPDATE_DNS_RULES_PROGRESS_DATA +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.ImportRulesManager.Companion.UPDATE_LOCAL_DNS_RULES_PROGRESS_ACTION +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.ImportRulesManager.Companion.UPDATE_REMOTE_DNS_RULES_PROGRESS_ACTION +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.ImportRulesManager.Companion.UPDATE_TOTAL_DNS_RULES_PROGRESS_ACTION +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.ImportRulesManager.Companion.UPDATE_TOTAL_DNS_RULES_PROGRESS_DATA +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.DnsRulesDownloadProgress +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.DnsRulesUpdateProgress +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.recycler.DnsRuleRecycleItem +import pan.alexander.tordnscrypt.utils.logger.Logger.loge +import java.util.Date +import javax.inject.Inject + +class DnsRulesReceiver @Inject constructor( + context: Context +) : BroadcastReceiver() { + + var callback: Callback? = null + + private var receiverRegistered = false + + private val localBroadcastManager by lazy { + LocalBroadcastManager.getInstance(context) + } + + fun registerReceiver() { + try { + receiverRegistered = true + localBroadcastManager.registerReceiver( + this, + IntentFilter(DOWNLOAD_REMOTE_DNS_RULES_PROGRESS_ACTION) + ) + localBroadcastManager.registerReceiver( + this, + IntentFilter(UPDATE_REMOTE_DNS_RULES_PROGRESS_ACTION) + ) + localBroadcastManager.registerReceiver( + this, + IntentFilter(UPDATE_LOCAL_DNS_RULES_PROGRESS_ACTION) + ) + localBroadcastManager.registerReceiver( + this, + IntentFilter(UPDATE_TOTAL_DNS_RULES_PROGRESS_ACTION) + ) + } catch (e: Exception) { + loge("DnsRulesReceiver registerReceiver", e) + } + } + + fun unregisterReceiver() { + try { + if (receiverRegistered) { + receiverRegistered = false + localBroadcastManager.unregisterReceiver(this) + } + } catch (e: Exception) { + loge("DnsRulesReceiver unregisterReceiver", e) + } + } + + override fun onReceive(context: Context?, intent: Intent?) { + when (intent?.action) { + DOWNLOAD_REMOTE_DNS_RULES_PROGRESS_ACTION -> downloadRemoteRulesProgress(intent) + UPDATE_REMOTE_DNS_RULES_PROGRESS_ACTION -> updateRemoteRulesProgress(intent) + UPDATE_LOCAL_DNS_RULES_PROGRESS_ACTION -> updateLocalRulesProgress(intent) + UPDATE_TOTAL_DNS_RULES_PROGRESS_ACTION -> updateTotalRulesProgress(intent) + } + } + + @Suppress("deprecation") + private fun downloadRemoteRulesProgress(intent: Intent) { + val data = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + intent.getParcelableExtra( + DOWNLOAD_REMOTE_DNS_RULES_PROGRESS_DATA, + DnsRulesDownloadProgress::class.java + ) + } else { + intent.getParcelableExtra( + DOWNLOAD_REMOTE_DNS_RULES_PROGRESS_DATA + ) + } + data ?: return + when (data) { + is DnsRulesDownloadProgress.DownloadProgress -> { + callback?.onUpdateRemoteRules( + DnsRuleRecycleItem.DnsRemoteRule( + name = data.name, + url = data.url, + date = Date(), + count = 0, + size = data.size, + inProgress = true + ) + ) + } + + is DnsRulesDownloadProgress.DownloadFinished -> { + callback?.onUpdateRemoteRules( + DnsRuleRecycleItem.DnsRemoteRule( + name = data.name, + url = data.url, + date = Date(), + count = 0, + size = data.size, + inProgress = false + ) + ) + } + + is DnsRulesDownloadProgress.DownloadFailure -> { + callback?.onUpdateRemoteRules( + DnsRuleRecycleItem.DnsRemoteRule( + name = data.name, + url = data.error, + date = Date(), + count = 0, + size = 0, + inProgress = false, + fault = true + ) + ) + } + } + } + + @Suppress("deprecation") + private fun updateRemoteRulesProgress(intent: Intent) { + val data = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + intent.getParcelableExtra( + UPDATE_DNS_RULES_PROGRESS_DATA, + DnsRulesUpdateProgress::class.java + ) + } else { + intent.getParcelableExtra( + UPDATE_DNS_RULES_PROGRESS_DATA + ) + } + data ?: return + when (data) { + is DnsRulesUpdateProgress.UpdateProgress -> { + callback?.onUpdateRemoteRules( + DnsRuleRecycleItem.DnsRemoteRule( + name = data.name, + url = data.url ?: "", + date = Date(), + count = data.count, + size = data.size, + inProgress = true + ) + ) + } + + is DnsRulesUpdateProgress.UpdateFinished -> { + callback?.onUpdateRemoteRules( + DnsRuleRecycleItem.DnsRemoteRule( + name = data.name, + url = data.url ?: "", + date = Date(), + count = data.count, + size = data.size, + inProgress = false + ) + ) + callback?.onUpdateFinished() + } + + is DnsRulesUpdateProgress.UpdateFailure -> { + callback?.onUpdateRemoteRules( + DnsRuleRecycleItem.DnsRemoteRule( + name = data.name, + url = data.url ?: "", + date = Date(), + count = 0, + size = 0, + inProgress = false, + fault = true + ) + ) + callback?.onUpdateFinished() + } + } + } + + @Suppress("deprecation") + private fun updateLocalRulesProgress(intent: Intent) { + val data = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + intent.getParcelableExtra( + UPDATE_DNS_RULES_PROGRESS_DATA, + DnsRulesUpdateProgress::class.java + ) + } else { + intent.getParcelableExtra( + UPDATE_DNS_RULES_PROGRESS_DATA + ) + } + data ?: return + when (data) { + is DnsRulesUpdateProgress.UpdateProgress -> { + callback?.onUpdateLocalRules( + DnsRuleRecycleItem.DnsLocalRule( + name = data.name, + date = Date(), + count = data.count, + size = data.size, + inProgress = true + ) + ) + } + + is DnsRulesUpdateProgress.UpdateFinished -> { + callback?.onUpdateLocalRules( + DnsRuleRecycleItem.DnsLocalRule( + name = data.name, + date = Date(), + count = data.count, + size = data.size, + inProgress = false + ) + ) + callback?.onUpdateFinished() + } + + is DnsRulesUpdateProgress.UpdateFailure -> { + callback?.onUpdateLocalRules( + DnsRuleRecycleItem.DnsLocalRule( + name = data.name, + date = Date(), + count = 0, + size = 0, + inProgress = false, + fault = true + ) + ) + callback?.onUpdateFinished() + } + } + } + + private fun updateTotalRulesProgress(intent: Intent) { + val count = intent.getIntExtra(UPDATE_TOTAL_DNS_RULES_PROGRESS_DATA, 0) + callback?.onUpdateTotalRules(count) + } + + interface Callback { + fun onUpdateRemoteRules(rules: DnsRuleRecycleItem.DnsRemoteRule) + fun onUpdateLocalRules(rules: DnsRuleRecycleItem.DnsLocalRule) + fun onUpdateTotalRules(count: Int) + fun onUpdateFinished() + } +} diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/recycler/DnsRuleRecycleItem.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/recycler/DnsRuleRecycleItem.kt new file mode 100644 index 000000000..c7bd3544f --- /dev/null +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/recycler/DnsRuleRecycleItem.kt @@ -0,0 +1,59 @@ +/* + This file is part of InviZible Pro. + + InviZible Pro is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + InviZible Pro is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with InviZible Pro. If not, see . + + Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com + */ + +package pan.alexander.tordnscrypt.settings.dnscrypt_rules.recycler + +import java.util.Date + +sealed class DnsRuleRecycleItem { + data class DnsRemoteRule( + val name: String, + val url: String, + val date: Date, + val count: Int, + val size: Long, + val inProgress: Boolean, + val fault: Boolean = false + ) : DnsRuleRecycleItem() + + data object AddRemoteRulesButton : DnsRuleRecycleItem() + + data class DnsLocalRule( + val name: String, + val date: Date, + val count: Int, + val size: Long, + val inProgress: Boolean, + val fault: Boolean = false + ) : DnsRuleRecycleItem() + + data object AddLocalRulesButton : DnsRuleRecycleItem() + + data class DnsSingleRule( + var rule: String, + val protected: Boolean, + val active: Boolean + ) : DnsRuleRecycleItem() + + data object AddSingleRuleButton : DnsRuleRecycleItem() + + data class DnsRuleComment( + val comment: String + ) : DnsRuleRecycleItem() +} diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/recycler/DnsRulesRecyclerAdapter.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/recycler/DnsRulesRecyclerAdapter.kt new file mode 100644 index 000000000..09166d4da --- /dev/null +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/recycler/DnsRulesRecyclerAdapter.kt @@ -0,0 +1,498 @@ +/* + This file is part of InviZible Pro. + + InviZible Pro is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + InviZible Pro is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with InviZible Pro. If not, see . + + Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com + */ + +package pan.alexander.tordnscrypt.settings.dnscrypt_rules.recycler + +import android.annotation.SuppressLint +import android.text.Editable +import android.text.TextWatcher +import android.view.LayoutInflater +import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE +import android.view.ViewGroup +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.NO_POSITION +import pan.alexander.tordnscrypt.R +import pan.alexander.tordnscrypt.databinding.ItemButtonBinding +import pan.alexander.tordnscrypt.databinding.ItemDnsRuleBinding +import pan.alexander.tordnscrypt.databinding.ItemRulesBinding +import pan.alexander.tordnscrypt.domain.dns_rules.DnsRuleType +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.ImportRulesManager +import pan.alexander.tordnscrypt.utils.Constants.IPv6_REGEX_NO_CAPTURING +import pan.alexander.tordnscrypt.utils.Utils +import pan.alexander.tordnscrypt.utils.logger.Logger.loge +import java.text.DateFormat +import kotlin.math.max + +private const val DNS_REMOTE_RULE = 1 +private const val DNS_REMOTE_RULE_BUTTON = 2 +private const val DNS_LOCAL_RULE = 3 +private const val DNS_LOCAL_RULE_BUTTON = 4 +private const val DNS_SINGLE_RULE = 5 +private const val DNS_SINGLE_RULE_BUTTON = 6 +private const val DNS_RULE_COMMENT = 7 + +class DnsRulesRecyclerAdapter( + private val onImportLocalRules: () -> Unit, + private val onLocalRulesDelete: () -> Unit, + private val onDeltaSingleRules: (Int) -> Unit, + private val onRemoteRulesAdd: () -> Unit, + private val onRemoteRulesDelete: () -> Unit, + private val onRemoteRulesRefresh: () -> Unit, +) : RecyclerView.Adapter() { + + var rulesType: DnsRuleType? = null + private val rules: MutableList = mutableListOf() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return try { + when (viewType) { + DNS_REMOTE_RULE, DNS_LOCAL_RULE -> { + val itemView = LayoutInflater.from(parent.context) + .inflate(R.layout.item_dns_rule, parent, false) + DnsRulesViewHolder(itemView) + } + + DNS_SINGLE_RULE -> { + val itemView = LayoutInflater.from(parent.context) + .inflate(R.layout.item_rules, parent, false) + DnsSingleRuleViewHolder(itemView) + } + + DNS_REMOTE_RULE_BUTTON, DNS_LOCAL_RULE_BUTTON, DNS_SINGLE_RULE_BUTTON -> { + val itemView = LayoutInflater.from(parent.context) + .inflate(R.layout.item_button, parent, false) + DnsRuleButtonViewHolder(itemView) + } + + else -> throw IllegalArgumentException("DnsRulesRecyclerAdapter unknown view type") + } + + } catch (e: Exception) { + loge("DnsRulesRecyclerAdapter onCreateViewHolder", e) + throw e + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder.itemViewType) { + DNS_REMOTE_RULE, DNS_LOCAL_RULE -> (holder as DnsRulesViewHolder).bind(position) + DNS_SINGLE_RULE -> (holder as DnsSingleRuleViewHolder).bind(position) + DNS_REMOTE_RULE_BUTTON, DNS_LOCAL_RULE_BUTTON, DNS_SINGLE_RULE_BUTTON -> + (holder as DnsRuleButtonViewHolder).bind(position) + + else -> throw IllegalArgumentException("DnsRulesRecyclerAdapter unknown view type") + } + } + + override fun getItemCount(): Int = rules.size + + override fun getItemViewType(position: Int): Int { + return when (rules[position]) { + is DnsRuleRecycleItem.DnsRemoteRule -> DNS_REMOTE_RULE + is DnsRuleRecycleItem.AddRemoteRulesButton -> DNS_REMOTE_RULE_BUTTON + is DnsRuleRecycleItem.DnsLocalRule -> DNS_LOCAL_RULE + is DnsRuleRecycleItem.AddLocalRulesButton -> DNS_LOCAL_RULE_BUTTON + is DnsRuleRecycleItem.DnsSingleRule -> DNS_SINGLE_RULE + is DnsRuleRecycleItem.AddSingleRuleButton -> DNS_SINGLE_RULE_BUTTON + is DnsRuleRecycleItem.DnsRuleComment -> DNS_RULE_COMMENT + } + } + + @SuppressLint("NotifyDataSetChanged") + fun updateRules(rules: List) { + this.rules.apply { + clear() + addAll(rules) + notifyDataSetChanged() + } + } + + fun updateRemoteRules(remoteRule: DnsRuleRecycleItem.DnsRemoteRule) { + for (i in rules.indices) { + val rule = rules[i] + if (rule is DnsRuleRecycleItem.DnsRemoteRule) { + rules[i] = remoteRule + notifyItemChanged(i, Any()) + break + } else if (rule is DnsRuleRecycleItem.AddRemoteRulesButton) { + rules.add(i, remoteRule) + notifyItemInserted(i) + break + } + } + } + + fun updateLocalRules(localRule: DnsRuleRecycleItem.DnsLocalRule) { + for (i in rules.indices) { + val rule = rules[i] + if (rule is DnsRuleRecycleItem.DnsLocalRule) { + rules[i] = localRule + notifyItemChanged(i, Any()) + break + } else if (rule is DnsRuleRecycleItem.AddLocalRulesButton) { + rules.add(i, localRule) + notifyItemInserted(i) + break + } + } + } + + fun getRules(): List = rules + + private inner class DnsRulesViewHolder(itemView: View) : BaseViewHolder(itemView) { + + private val textColorGreen by lazy { + ContextCompat.getColor(itemView.context, R.color.colorGreen) + } + + private val textColorSecondary by lazy { + ContextCompat.getColor(itemView.context, R.color.colorTextSecondary) + } + + private val faultColorFault by lazy { + ContextCompat.getColor(itemView.context, R.color.colorAlert) + } + + override fun bind(position: Int) { + when (val rule = rules[position]) { + is DnsRuleRecycleItem.DnsRemoteRule -> { + ItemDnsRuleBinding.bind(itemView).apply { + tvDnsRuleFileName.text = rule.name + tvDnsRuleUrl.visibility = VISIBLE + tvDnsRuleUrl.text = rule.url + if (rule.fault) { + tvDnsRuleUrl.setTextColor(faultColorFault) + } else { + tvDnsRuleUrl.setTextColor(textColorSecondary) + } + tvDnsRuleFileDate.text = + DateFormat.getDateInstance(DateFormat.SHORT).format(rule.date) + if (rule.fault) { + tvDnsRuleFileDate.setTextColor(faultColorFault) + } else { + tvDnsRuleFileDate.setTextColor(textColorGreen) + } + tvDnsRuleFileSize.text = Utils.formatFileSizeToReadableUnits(rule.size) + if (rule.size == 0L) { + tvDnsRuleFileSize.setTextColor(faultColorFault) + } else { + tvDnsRuleFileSize.setTextColor(textColorGreen) + } + tvDnsRuleFileQuantity.text = rule.count.toString() + if (rule.count == 0) { + tvDnsRuleFileQuantity.setTextColor(faultColorFault) + tvDnsRuleFileQuantityRules.setTextColor(faultColorFault) + } else { + tvDnsRuleFileQuantity.setTextColor(textColorGreen) + tvDnsRuleFileQuantityRules.setTextColor(textColorGreen) + } + btnDnsRuleFileDelete.setOnClickListener { deleteRemoteRules() } + btnDnsRuleFileRefresh.setOnClickListener { onRemoteRulesRefresh() } + if (rule.inProgress) { + pbDnsRule.visibility = VISIBLE + } else { + pbDnsRule.visibility = GONE + } + } + } + + is DnsRuleRecycleItem.DnsLocalRule -> { + ItemDnsRuleBinding.bind(itemView).apply { + val name = rule.name.ifEmpty { "local-rules.txt" } + tvDnsRuleFileName.text = name + tvDnsRuleUrl.visibility = GONE + tvDnsRuleFileDate.text = + DateFormat.getDateInstance(DateFormat.SHORT).format(rule.date) + if (rule.fault) { + tvDnsRuleFileDate.setTextColor(faultColorFault) + } else { + tvDnsRuleFileDate.setTextColor(textColorGreen) + } + tvDnsRuleFileSize.text = Utils.formatFileSizeToReadableUnits(rule.size) + if (rule.size == 0L) { + tvDnsRuleFileSize.setTextColor(faultColorFault) + } else { + tvDnsRuleFileSize.setTextColor(textColorGreen) + } + tvDnsRuleFileQuantity.text = rule.count.toString() + if (rule.count == 0) { + tvDnsRuleFileQuantity.setTextColor(faultColorFault) + tvDnsRuleFileQuantityRules.setTextColor(faultColorFault) + } else { + tvDnsRuleFileQuantity.setTextColor(textColorGreen) + tvDnsRuleFileQuantityRules.setTextColor(textColorGreen) + } + btnDnsRuleFileDelete.setOnClickListener { deleteLocalRules() } + btnDnsRuleFileRefresh.visibility = GONE + if (rule.inProgress) { + pbDnsRule.visibility = VISIBLE + pbDnsRule.isIndeterminate = true + } else { + pbDnsRule.isIndeterminate = false + pbDnsRule.visibility = GONE + } + } + } + + else -> Unit + } + } + + private fun deleteRemoteRules() { + val adapterPosition = bindingAdapterPosition + if (adapterPosition != NO_POSITION) { + rules.removeAt(adapterPosition) + onRemoteRulesDelete() + notifyItemRemoved(adapterPosition) + } + } + + private fun deleteLocalRules() { + val adapterPosition = bindingAdapterPosition + if (adapterPosition != NO_POSITION) { + rules.removeAt(adapterPosition) + onLocalRulesDelete() + notifyItemRemoved(adapterPosition) + } + } + } + + private inner class DnsSingleRuleViewHolder(itemView: View) : BaseViewHolder(itemView), + View.OnClickListener { + + val watcher = object : TextWatcher { + override fun beforeTextChanged( + s: CharSequence?, + start: Int, + count: Int, + after: Int + ) { + } + + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { + } + + override fun afterTextChanged(s: Editable?) { + val position = bindingAdapterPosition + if (s == null || position == NO_POSITION) { + return + } + + editRule(position, s.toString()) + } + + } + + override fun bind(position: Int) { + when (val rule = rules[position]) { + is DnsRuleRecycleItem.DnsSingleRule -> { + ItemRulesBinding.bind(itemView).apply { + etRule.setText(rule.rule, TextView.BufferType.EDITABLE) + etRule.isEnabled = rule.active + etRule.addTextChangedListener(watcher) + swRuleActive.isChecked = rule.active + swRuleActive.setOnClickListener(this@DnsSingleRuleViewHolder) + if (rule.protected) { + delBtnRules.visibility = GONE + } else { + delBtnRules.visibility = VISIBLE + delBtnRules.setOnClickListener(this@DnsSingleRuleViewHolder) + } + } + } + + else -> Unit + } + } + + override fun onClick(v: View?) { + val position = bindingAdapterPosition + if (position == NO_POSITION) { + return + } + + when (v?.id) { + R.id.delBtnRules -> deleteRule(position) + R.id.swRuleActive -> toggleRule(position) + } + } + + } + + private fun addRule() { + + for (rule in rules) { + if (rule is DnsRuleRecycleItem.DnsSingleRule && rule.rule.isBlank()) { + return + } + } + + rules.add( + max(rules.size - 1, 0), + DnsRuleRecycleItem.DnsSingleRule( + rule = "", + protected = false, + active = true + ) + ) + onDeltaSingleRules(1) + notifyItemInserted(rules.size - 1) + } + + private fun toggleRule(position: Int) { + val rule = rules[position] + if (rule is DnsRuleRecycleItem.DnsSingleRule) { + rules[position] = DnsRuleRecycleItem.DnsSingleRule( + rule = rule.rule, + protected = rule.protected, + active = !rule.active + ) + onDeltaSingleRules(if (rule.active) -1 else 1) + notifyItemChanged(position) + } + } + + private fun editRule(position: Int, text: String) { + val rule = rules[position] + if (rule is DnsRuleRecycleItem.DnsSingleRule) { + if (text.contains("\n")) { + text.split("\n").map { + it.trim() + }.map { + when (rulesType) { + DnsRuleType.BLACKLIST, DnsRuleType.WHITELIST -> + Utils.getDomainNameFromUrl(it) + + DnsRuleType.IP_BLACKLIST -> prepareIPv6IfAny(it) + + else -> it + } + }.filter { + it.isNotBlank() && it.removePrefix("#").matches(getRuleRegex()) + }.takeIf { + it.isNotEmpty() + }?.also { + rules[position] = DnsRuleRecycleItem.DnsSingleRule( + rule = it.first().removePrefix("#"), + protected = false, + active = !it.first().startsWith("#") + ) + notifyItemChanged(position) + }?.drop(1)?.forEachIndexed { index, s -> + rules.add( + position + index + 1, + DnsRuleRecycleItem.DnsSingleRule( + rule = s.removePrefix("#"), + protected = false, + active = !s.startsWith("#") + ) + ) + notifyItemInserted(position + index + 1) + } + } else { + val textPrepared = when (rulesType) { + DnsRuleType.BLACKLIST, DnsRuleType.WHITELIST -> Utils.getDomainNameFromUrl(text) + DnsRuleType.IP_BLACKLIST -> prepareIPv6IfAny(text) + else -> text + } + if (textPrepared.matches(getRuleRegex())) { + rule.rule = textPrepared + if (textPrepared != text) { + notifyItemChanged(position) + } + } + } + } + } + + private fun prepareIPv6IfAny(line: String): String = + if (line.matches(Regex(IPv6_REGEX_NO_CAPTURING))) { + "[$line]" + } else { + line + } + + private fun getRuleRegex() = when (rulesType) { + DnsRuleType.BLACKLIST -> ImportRulesManager.DnsRulesRegex.blackListHostRulesRegex + DnsRuleType.WHITELIST -> ImportRulesManager.DnsRulesRegex.whiteListHostRulesRegex + DnsRuleType.IP_BLACKLIST -> ImportRulesManager.DnsRulesRegex.blacklistIPRulesRegex + DnsRuleType.FORWARDING -> ImportRulesManager.DnsRulesRegex.forwardingRulesRegex + DnsRuleType.CLOAKING -> ImportRulesManager.DnsRulesRegex.cloakingRulesRegex + null -> throw IllegalArgumentException("DnsRulesRecyclerAdapter getRuleRegex rulesType null") + } + + private fun deleteRule(position: Int) { + rules.removeAt(position) + onDeltaSingleRules(-1) + notifyItemRemoved(position) + } + + private inner class DnsRuleButtonViewHolder(itemView: View) : BaseViewHolder(itemView) { + + override fun bind(position: Int) { + when (rules[position]) { + is DnsRuleRecycleItem.AddRemoteRulesButton -> { + ItemButtonBinding.bind(itemView).apply { + if (rules.firstOrNull { it is DnsRuleRecycleItem.DnsRemoteRule } == null) { + btnAddRuleItem.setText(R.string.dns_rule_add_remote_list) + } else { + btnAddRuleItem.setText(R.string.dns_rule_replace_remote_list) + } + btnAddRuleItem.setOnClickListener { + onRemoteRulesAdd() + } + } + } + + is DnsRuleRecycleItem.AddLocalRulesButton -> { + ItemButtonBinding.bind(itemView).apply { + if (rules.firstOrNull { it is DnsRuleRecycleItem.DnsLocalRule } == null) { + btnAddRuleItem.setText(R.string.dns_rule_add_local_list) + } else { + btnAddRuleItem.setText(R.string.dns_rule_replace_local_list) + } + btnAddRuleItem.setOnClickListener { + onImportLocalRules() + } + } + } + + is DnsRuleRecycleItem.AddSingleRuleButton -> { + ItemButtonBinding.bind(itemView).apply { + btnAddRuleItem.setText(R.string.dns_rule_add_rule) + btnAddRuleItem.setOnClickListener { + addRule() + } + } + } + + else -> Unit + } + } + } + + private abstract class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + abstract fun bind(position: Int) + } + +} diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/remote/DnsRulesDownloadProgress.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/remote/DnsRulesDownloadProgress.kt new file mode 100644 index 000000000..602f1eeb6 --- /dev/null +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/remote/DnsRulesDownloadProgress.kt @@ -0,0 +1,46 @@ +/* + This file is part of InviZible Pro. + + InviZible Pro is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + InviZible Pro is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with InviZible Pro. If not, see . + + Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com + */ + +package pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +sealed class DnsRulesDownloadProgress : Parcelable { + + data class DownloadProgress( + val name: String, + val url: String, + val size: Long, + val progress: Int + ): DnsRulesDownloadProgress() + + data class DownloadFinished( + val name: String, + val url: String, + val size: Long + ): DnsRulesDownloadProgress() + + data class DownloadFailure( + val name: String, + val url: String, + val error: String + ): DnsRulesDownloadProgress() +} diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/remote/DownloadRemoteRulesManager.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/remote/DownloadRemoteRulesManager.kt new file mode 100644 index 000000000..5fbd55c22 --- /dev/null +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/remote/DownloadRemoteRulesManager.kt @@ -0,0 +1,206 @@ +/* + This file is part of InviZible Pro. + + InviZible Pro is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + InviZible Pro is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with InviZible Pro. If not, see . + + Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com + */ + +package pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote + +import android.content.Context +import android.content.Intent +import android.os.Build +import androidx.localbroadcastmanager.content.LocalBroadcastManager +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.isActive +import kotlinx.coroutines.withContext +import pan.alexander.tordnscrypt.di.CoroutinesModule +import pan.alexander.tordnscrypt.settings.PathVars +import pan.alexander.tordnscrypt.utils.Constants.TOR_BROWSER_USER_AGENT +import pan.alexander.tordnscrypt.utils.logger.Logger.loge +import pan.alexander.tordnscrypt.utils.logger.Logger.logi +import pan.alexander.tordnscrypt.utils.logger.Logger.logw +import pan.alexander.tordnscrypt.utils.web.HttpsConnectionManager +import java.io.File +import java.io.IOException +import javax.inject.Inject +import javax.inject.Named + +private const val READ_TIMEOUT_SEC = 30 +private const val CONNECT_TIMEOUT_SEC = 30 +private const val ATTEMPTS_TO_DOWNLOAD = 5 +private const val TIME_TO_DOWNLOAD_MINUTES = 10 +private const val ATTEMPTS_TO_DOWNLOAD_WITHIN_TIME = 20 +private const val UPDATE_PROGRESS_INTERVAL_MSEC = 300 + +class DownloadRemoteRulesManager @Inject constructor( + private val context: Context, + private val pathVars: PathVars, + @Named(CoroutinesModule.DISPATCHER_IO) + private val dispatcherIo: CoroutineDispatcher, + private val httpsConnectionManager: HttpsConnectionManager +) { + + private val localBroadcastManager by lazy { + LocalBroadcastManager.getInstance(context) + } + + suspend fun downloadRules(ruleName: String, url: String, fileName: String): File? = + withContext(dispatcherIo) { + var attempts = 0 + val startTime = System.currentTimeMillis() + var outputFile: File? = null + var error = "" + try { + val path = "${pathVars.getCacheDirPath(context)}/$fileName" + val oldFile = File(path) + if (oldFile.isFile) { + oldFile.delete() + } + outputFile = File(path).apply { + createNewFile() + } + do { + attempts++ + try { + outputFile = tryDownload(ruleName, url, path) + } catch (e: IOException) { + error = e.message ?: "" + logw( + "DownloadRulesManager failed to download file $url, attempt $attempts", + e + ) + } + } while ( + outputFile == null && isActive && + (attempts < ATTEMPTS_TO_DOWNLOAD + || System.currentTimeMillis() - startTime < TIME_TO_DOWNLOAD_MINUTES * 60000 + && attempts < ATTEMPTS_TO_DOWNLOAD_WITHIN_TIME) + ) + } catch (e: Exception) { + error = e.message ?: "" + loge("DownloadRulesManager failed to download file $url", e) + } + if (outputFile != null && outputFile.length() > 0) { + sendDownloadFinishedBroadcast(ruleName, url, outputFile.length()) + logi("Downloading $url was successful") + } else { + sendDownloadFailedBroadcast(ruleName, url, error) + } + return@withContext outputFile + } + + private suspend fun tryDownload(ruleName: String, url: String, filePath: String): File? = + withContext(dispatcherIo) { + logi("Downloading DNSCrypt rules $url") + + var range: Long = 0 + val file = File(filePath) + if (file.isFile) { + range = file.length() + } else { + file.createNewFile() + } + + val connection = httpsConnectionManager.getHttpsUrlConnection(url).apply { + connectTimeout = CONNECT_TIMEOUT_SEC * 1000 + readTimeout = READ_TIMEOUT_SEC * 1000 + setRequestProperty("User-Agent", TOR_BROWSER_USER_AGENT) + if (range != 0L) { + setRequestProperty("Range", "bytes=$range-") + } + } + val fileLength = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + connection.contentLengthLong + range + } else { + connection.getContentLength() + range + } + connection.inputStream.buffered().use { input -> + val data = ByteArray(1024) + file.outputStream().use { output -> + var time = System.currentTimeMillis() + var count = input.read(data) + while (count != -1 && isActive) { + range += count + val percent = (range * 100 / fileLength).toInt() + val currentTime = System.currentTimeMillis() + if (currentTime - time > UPDATE_PROGRESS_INTERVAL_MSEC) { + time = currentTime + sendUpdateProgressBroadcast(ruleName, url, range, percent) + } + output.write(data, 0, count) + count = input.read(data) + } + } + } + connection.disconnect() + return@withContext if (isActive) { + file + } else { + file.delete() + null + } + } + + private fun sendUpdateProgressBroadcast( + name: String, + url: String, + size: Long, + progress: Int + ) { + val intent = Intent(DOWNLOAD_REMOTE_DNS_RULES_PROGRESS_ACTION).apply { + putExtra( + DOWNLOAD_REMOTE_DNS_RULES_PROGRESS_DATA, + DnsRulesDownloadProgress.DownloadProgress(name, url, size, progress) + ) + } + localBroadcastManager.sendBroadcast(intent) + } + + private fun sendDownloadFinishedBroadcast( + name: String, + url: String, + size: Long + ) { + val intent = Intent(DOWNLOAD_REMOTE_DNS_RULES_PROGRESS_ACTION).apply { + putExtra( + DOWNLOAD_REMOTE_DNS_RULES_PROGRESS_DATA, + DnsRulesDownloadProgress.DownloadFinished(name, url, size) + ) + } + localBroadcastManager.sendBroadcast(intent) + } + + private fun sendDownloadFailedBroadcast( + name: String, + url: String, + error: String + ) { + val intent = Intent(DOWNLOAD_REMOTE_DNS_RULES_PROGRESS_ACTION).apply { + putExtra( + DOWNLOAD_REMOTE_DNS_RULES_PROGRESS_DATA, + DnsRulesDownloadProgress.DownloadFailure(name, url, error) + ) + } + localBroadcastManager.sendBroadcast(intent) + } + + companion object { + const val DOWNLOAD_REMOTE_DNS_RULES_PROGRESS_ACTION = + "pan.alexander.tordnscrypt.DOWNLOAD_REMOTE_DNS_RULES_PROGRESS_ACTION" + const val DOWNLOAD_REMOTE_DNS_RULES_PROGRESS_DATA = + "pan.alexander.tordnscrypt.DOWNLOAD_REMOTE_DNS_RULES_PROGRESS_DATA" + } +} diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/remote/UpdateRemoteDnsRulesWorker.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/remote/UpdateRemoteDnsRulesWorker.kt new file mode 100644 index 000000000..63e1df998 --- /dev/null +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/remote/UpdateRemoteDnsRulesWorker.kt @@ -0,0 +1,147 @@ +/* + This file is part of InviZible Pro. + + InviZible Pro is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + InviZible Pro is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with InviZible Pro. If not, see . + + Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com + */ + +package pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote + +import android.content.Context +import androidx.work.CoroutineWorker +import androidx.work.WorkInfo +import androidx.work.WorkManager +import androidx.work.WorkerParameters +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.delay +import kotlinx.coroutines.runInterruptible +import pan.alexander.tordnscrypt.App +import pan.alexander.tordnscrypt.di.CoroutinesModule +import pan.alexander.tordnscrypt.domain.dns_rules.DnsRuleType +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.ImportRulesManager +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.UpdateLocalRulesWorkManager.Companion.REFRESH_LOCAL_DNS_BLACKLIST_WORK +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.UpdateLocalRulesWorkManager.Companion.REFRESH_LOCAL_DNS_CLOAKING_WORK +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.UpdateLocalRulesWorkManager.Companion.REFRESH_LOCAL_DNS_FORWARDING_WORK +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.UpdateLocalRulesWorkManager.Companion.REFRESH_LOCAL_DNS_IP_BLACKLIST_WORK +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.UpdateLocalRulesWorkManager.Companion.REFRESH_LOCAL_DNS_WHITELIST_WORK +import pan.alexander.tordnscrypt.utils.logger.Logger.loge +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteRulesWorkManager.Companion.REMOTE_RULES_NAME_ARG +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteRulesWorkManager.Companion.REMOTE_RULES_TYPE_ARG +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteRulesWorkManager.Companion.REMOTE_RULES_URL_ARG +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.existing.RemixExistingRulesWorkManager.Companion.MIX_DNS_BLACKLIST_WORK +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.existing.RemixExistingRulesWorkManager.Companion.MIX_DNS_CLOAKING_WORK +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.existing.RemixExistingRulesWorkManager.Companion.MIX_DNS_FORWARDING_WORK +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.existing.RemixExistingRulesWorkManager.Companion.MIX_DNS_IP_BLACKLIST_WORK +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.existing.RemixExistingRulesWorkManager.Companion.MIX_DNS_WHITELIST_WORK +import java.io.File +import javax.inject.Inject +import javax.inject.Named + +class UpdateRemoteDnsRulesWorker(private val appContext: Context, workerParams: WorkerParameters) : + CoroutineWorker(appContext, workerParams) { + + init { + App.instance.daggerComponent.inject(this) + } + + @Inject + @Named(CoroutinesModule.DISPATCHER_IO) + lateinit var dispatcherIo: CoroutineDispatcher + + @Inject + lateinit var downloadRulesManager: DownloadRemoteRulesManager + + override suspend fun doWork(): Result { + try { + val url = inputData.getString(REMOTE_RULES_URL_ARG) ?: return Result.failure() + val ruleType = inputData.getString(REMOTE_RULES_TYPE_ARG)?.let { + DnsRuleType.valueOf(it) + } ?: return Result.failure() + val ruleName = inputData.getString(REMOTE_RULES_NAME_ARG) ?: return Result.failure() + val fileName = when (ruleType) { + DnsRuleType.BLACKLIST -> "blacklist-remote.txt" + DnsRuleType.WHITELIST -> "whitelist-remote.txt" + DnsRuleType.IP_BLACKLIST -> "ip-blacklist-remote.txt" + DnsRuleType.FORWARDING -> "forwarding-rules-remote.txt" + DnsRuleType.CLOAKING -> "cloaking-rules-remote.txt" + } + val file = downloadRulesManager.downloadRules(ruleName, url, fileName) + + while (isLocalDnsRulesImportingInProgress(ruleType) + || isMixDnsRulesInProgress(ruleType) + ) { + delay(500) + } + + file?.let { + if (it.length() > 0) { + importRulesFromFile(it, ruleType, url, ruleName) + } else { + return Result.retry() + } + } ?: return Result.failure() + return Result.success() + } catch (e: Exception) { + loge("UpdateRemoteDnsRulesWorker doWork", e) + } + return Result.failure() + } + + private suspend fun importRulesFromFile( + file: File, + ruleType: DnsRuleType, + url: String, + ruleName: String + ) = runInterruptible(dispatcherIo) { + ImportRulesManager( + context = appContext, + rulesVariant = ruleType, + remoteRulesUrl = url, + remoteRulesName = ruleName, + importType = ImportRulesManager.ImportType.REMOTE_RULES, + filePathToImport = arrayOf(file.path) + ).run() + } + + private fun isLocalDnsRulesImportingInProgress(type: DnsRuleType): Boolean = + WorkManager.getInstance(appContext) + .getWorkInfosForUniqueWork(getLocalWorkName(type)).get() + .firstOrNull()?.state == WorkInfo.State.RUNNING + + + private fun getLocalWorkName(type: DnsRuleType) = + when (type) { + DnsRuleType.BLACKLIST -> REFRESH_LOCAL_DNS_BLACKLIST_WORK + DnsRuleType.WHITELIST -> REFRESH_LOCAL_DNS_WHITELIST_WORK + DnsRuleType.IP_BLACKLIST -> REFRESH_LOCAL_DNS_IP_BLACKLIST_WORK + DnsRuleType.FORWARDING -> REFRESH_LOCAL_DNS_FORWARDING_WORK + DnsRuleType.CLOAKING -> REFRESH_LOCAL_DNS_CLOAKING_WORK + } + + private fun isMixDnsRulesInProgress(type: DnsRuleType): Boolean = + WorkManager.getInstance(appContext) + .getWorkInfosForUniqueWork(getMixWorkName(type)).get() + .firstOrNull()?.state == WorkInfo.State.RUNNING + + private fun getMixWorkName(type: DnsRuleType) = + when (type) { + DnsRuleType.BLACKLIST -> MIX_DNS_BLACKLIST_WORK + DnsRuleType.WHITELIST -> MIX_DNS_WHITELIST_WORK + DnsRuleType.IP_BLACKLIST -> MIX_DNS_IP_BLACKLIST_WORK + DnsRuleType.FORWARDING -> MIX_DNS_FORWARDING_WORK + DnsRuleType.CLOAKING -> MIX_DNS_CLOAKING_WORK + } + +} diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/remote/UpdateRemoteRulesWorkManager.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/remote/UpdateRemoteRulesWorkManager.kt new file mode 100644 index 000000000..2faf4892d --- /dev/null +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/remote/UpdateRemoteRulesWorkManager.kt @@ -0,0 +1,193 @@ +/* + This file is part of InviZible Pro. + + InviZible Pro is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + InviZible Pro is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with InviZible Pro. If not, see . + + Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com + */ + +package pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote + +import android.content.Context +import android.content.SharedPreferences +import androidx.work.BackoffPolicy +import androidx.work.Constraints +import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.NetworkType +import androidx.work.PeriodicWorkRequestBuilder +import androidx.work.WorkInfo +import androidx.work.WorkManager +import androidx.work.WorkRequest.Companion.DEFAULT_BACKOFF_DELAY_MILLIS +import androidx.work.workDataOf +import pan.alexander.tordnscrypt.di.SharedPreferencesModule +import pan.alexander.tordnscrypt.domain.preferences.PreferenceRepository +import pan.alexander.tordnscrypt.domain.dns_rules.DnsRuleType +import pan.alexander.tordnscrypt.utils.Utils.getDomainNameFromUrl +import pan.alexander.tordnscrypt.utils.logger.Logger.loge +import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.DNSCRYPT_RULES_REFRESH_DELAY +import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REMOTE_BLACKLIST_URL +import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REMOTE_CLOAKING_URL +import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REMOTE_FORWARDING_URL +import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REMOTE_IP_BLACKLIST_URL +import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REMOTE_WHITELIST_URL +import java.util.concurrent.TimeUnit +import javax.inject.Inject +import javax.inject.Named + +private const val DEFAULT_DELAY_HOURS = 72 + +class UpdateRemoteRulesWorkManager @Inject constructor( + private val context: Context, + @Named(SharedPreferencesModule.DEFAULT_PREFERENCES_NAME) + private val defaultPreferences: SharedPreferences, + private val preferences: PreferenceRepository +) { + + private val workManager by lazy { WorkManager.getInstance(context) } + + fun startRefreshDnsRules(ruleName: String, ruleType: DnsRuleType) { + + val interval = getInterval() + if (interval == 0L) { + return + } + + val updateRequest = + PeriodicWorkRequestBuilder(interval, TimeUnit.HOURS) + .setConstraints(getConstraints()) + .setBackoffCriteria( + BackoffPolicy.EXPONENTIAL, + DEFAULT_BACKOFF_DELAY_MILLIS, + TimeUnit.MILLISECONDS + ) + .setInputData( + workDataOf( + REMOTE_RULES_TYPE_ARG to ruleType.name, + REMOTE_RULES_NAME_ARG to ruleName, + REMOTE_RULES_URL_ARG to getRuleUrl(ruleType) + ) + ) + .build() + + workManager.enqueueUniquePeriodicWork( + getWorkName(ruleType), + ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, + updateRequest + ) + } + + fun updateRefreshDnsRulesInterval(interval: Long) = + DnsRuleType.entries.forEach { type -> + val workInfos = workManager.getWorkInfosForUniqueWork(getWorkName(type)) + workInfos.get().firstOrNull()?.let { workInfo -> + try { + if (interval == 0L) { + stopRefreshDnsRules(type) + } else { + updateExistingWorkInterval(type, workInfo, interval) + } + } catch (e: Exception) { + loge("UpdateRemoteRulesWorkManager updateRefreshDnsRulesInterval", e) + } + } + } + + private fun updateExistingWorkInterval( + ruleType: DnsRuleType, + workInfo: WorkInfo, + interval: Long + ) { + val updateRequest = + PeriodicWorkRequestBuilder(interval, TimeUnit.HOURS) + .setConstraints(getConstraints()) + .setId(workInfo.id) + .setBackoffCriteria( + BackoffPolicy.EXPONENTIAL, + DEFAULT_BACKOFF_DELAY_MILLIS, + TimeUnit.MILLISECONDS + ) + .setInputData( + workDataOf( + REMOTE_RULES_TYPE_ARG to ruleType.name, + REMOTE_RULES_NAME_ARG to getDomainNameFromUrl(getRuleUrl(ruleType)), + REMOTE_RULES_URL_ARG to getRuleUrl(ruleType) + ) + ) + .build() + + workManager.updateWork(updateRequest) + } + + fun stopRefreshDnsRules(type: DnsRuleType) { + + val interval = getInterval() + if (interval == 0L) { + return + } + + workManager.cancelUniqueWork(getWorkName(type)) + } + + private fun getInterval(): Long = try { + val refreshPeriod = defaultPreferences.getString( + DNSCRYPT_RULES_REFRESH_DELAY, + DEFAULT_DELAY_HOURS.toString() + ) + refreshPeriod?.toLong() ?: DEFAULT_DELAY_HOURS.toLong() + } catch (e: Exception) { + loge("UpdateDnsRulesManager getInterval", e) + DEFAULT_DELAY_HOURS.toLong() + } + + private fun getConstraints() = Constraints.Builder() + .setRequiresBatteryNotLow(true) + .setRequiresStorageNotLow(true) + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() + + private fun getWorkName(type: DnsRuleType) = + when (type) { + DnsRuleType.BLACKLIST -> REFRESH_REMOTE_DNS_BLACKLIST_WORK + DnsRuleType.WHITELIST -> REFRESH_REMOTE_DNS_WHITELIST_WORK + DnsRuleType.IP_BLACKLIST -> REFRESH_REMOTE_DNS_IP_BLACKLIST_WORK + DnsRuleType.FORWARDING -> REFRESH_REMOTE_DNS_FORWARDING_WORK + DnsRuleType.CLOAKING -> REFRESH_REMOTE_DNS_CLOAKING_WORK + } + + private fun getRuleUrl(type: DnsRuleType) = + when (type) { + DnsRuleType.BLACKLIST -> preferences.getStringPreference(REMOTE_BLACKLIST_URL) + DnsRuleType.WHITELIST -> preferences.getStringPreference(REMOTE_WHITELIST_URL) + DnsRuleType.IP_BLACKLIST -> preferences.getStringPreference(REMOTE_IP_BLACKLIST_URL) + DnsRuleType.FORWARDING -> preferences.getStringPreference(REMOTE_FORWARDING_URL) + DnsRuleType.CLOAKING -> preferences.getStringPreference(REMOTE_CLOAKING_URL) + } + + companion object { + const val REMOTE_RULES_URL_ARG = "pan.alexander.tordnscrypt.REMOTE_RULES_URL_ARG" + const val REMOTE_RULES_NAME_ARG = "pan.alexander.tordnscrypt.REMOTE_RULES_NAME_ARG" + const val REMOTE_RULES_TYPE_ARG = "pan.alexander.tordnscrypt.REMOTE_RULES_TYPE_ARG" + + const val REFRESH_REMOTE_DNS_BLACKLIST_WORK = + "pan.alexander.tordnscrypt.REFRESH_REMOTE_DNS_BLACKLIST_WORK" + const val REFRESH_REMOTE_DNS_WHITELIST_WORK = + "pan.alexander.tordnscrypt.REFRESH_REMOTE_DNS_WHITELIST_WORK" + const val REFRESH_REMOTE_DNS_IP_BLACKLIST_WORK = + "pan.alexander.tordnscrypt.REFRESH_REMOTE_DNS_IP_BLACKLIST_WORK" + const val REFRESH_REMOTE_DNS_FORWARDING_WORK = + "pan.alexander.tordnscrypt.REFRESH_REMOTE_DNS_FORWARDING_WORK" + const val REFRESH_REMOTE_DNS_CLOAKING_WORK = + "pan.alexander.tordnscrypt.REFRESH_REMOTE_DNS_CLOAKING_WORK" + } +} diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_settings/EraseRules.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_settings/EraseRules.kt deleted file mode 100644 index 32033b480..000000000 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_settings/EraseRules.kt +++ /dev/null @@ -1,110 +0,0 @@ -/* - This file is part of InviZible Pro. - - InviZible Pro is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - InviZible Pro is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with InviZible Pro. If not, see . - - Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com - */ - -package pan.alexander.tordnscrypt.settings.dnscrypt_settings - -import android.content.Context -import androidx.fragment.app.FragmentManager -import androidx.preference.PreferenceManager -import pan.alexander.tordnscrypt.App -import pan.alexander.tordnscrypt.R -import pan.alexander.tordnscrypt.dialogs.NotificationDialogFragment -import pan.alexander.tordnscrypt.modules.ModulesRestarter -import pan.alexander.tordnscrypt.modules.ModulesStatus -import pan.alexander.tordnscrypt.utils.enums.DNSCryptRulesVariant -import pan.alexander.tordnscrypt.utils.enums.ModuleState -import pan.alexander.tordnscrypt.utils.logger.Logger.loge -import java.io.File -import java.lang.Exception - -class EraseRules(private val context: Context, - private val fragmentManager: FragmentManager, - private val dnsCryptRulesVariant: DNSCryptRulesVariant, - private val remoteRulesLinkPreferenceTag: String) : Thread() { - - override fun run() { - val pathVars = App.instance.daggerComponent.getPathVars().get() - - when(dnsCryptRulesVariant) { - - DNSCryptRulesVariant.BLACKLIST_HOSTS -> eraseRuleVariant(pathVars.dnsCryptBlackListPath, - pathVars.dnsCryptLocalBlackListPath, pathVars.dnsCryptRemoteBlackListPath) - - DNSCryptRulesVariant.BLACKLIST_IPS -> eraseRuleVariant(pathVars.dnsCryptIPBlackListPath, - pathVars.dnsCryptLocalIPBlackListPath, pathVars.dnsCryptRemoteIPBlackListPath) - - DNSCryptRulesVariant.WHITELIST_HOSTS -> eraseRuleVariant(pathVars.dnsCryptWhiteListPath, - pathVars.dnsCryptLocalWhiteListPath, pathVars.dnsCryptRemoteWhiteListPath) - - DNSCryptRulesVariant.CLOAKING -> eraseRuleVariant(pathVars.dnsCryptCloakingRulesPath, - pathVars.dnsCryptLocalCloakingRulesPath, pathVars.dnsCryptRemoteCloakingRulesPath) - - DNSCryptRulesVariant.FORWARDING -> eraseRuleVariant(pathVars.dnsCryptForwardingRulesPath, - pathVars.dnsCryptLocalForwardingRulesPath, pathVars.dnsCryptRemoteForwardingRulesPath) - - DNSCryptRulesVariant.UNDEFINED -> return - - } - } - - private fun eraseRuleVariant(rulesFilePath: String, localRulesFilePath: String, remoteRulesFilePath: String) { - eraseFile(rulesFilePath) - eraseFile(localRulesFilePath) - eraseFile(remoteRulesFilePath) - erasePreference() - restartDNSCryptIfRequired() - showFinalDialog() - } - - private fun eraseFile(filePath: String) { - - var eraseText = "" - if (dnsCryptRulesVariant == DNSCryptRulesVariant.CLOAKING) { - eraseText = "*i2p 10.191.0.1" - } else if (dnsCryptRulesVariant == DNSCryptRulesVariant.FORWARDING) { - eraseText = "onion 127.0.0.1:" + App.instance.daggerComponent.getPathVars().get().torDNSPort - } - - try { - val file = File(filePath) - if (file.isFile) { - file.writeText(eraseText) - } - } catch (e: Exception) { - loge("EraseRules", e) - } - - } - - private fun erasePreference() { - val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) - sharedPreferences.edit().putString(remoteRulesLinkPreferenceTag, "").apply() - } - - private fun showFinalDialog() { - val dialog = NotificationDialogFragment.newInstance(R.string.erase_dnscrypt_rules_dialog_message) - dialog.show(fragmentManager, "EraseDialog") - } - - private fun restartDNSCryptIfRequired() { - if (ModulesStatus.getInstance().dnsCryptState == ModuleState.RUNNING) { - ModulesRestarter.restartDNSCrypt(context) - } - } -} \ No newline at end of file diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_settings/ImportRules.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_settings/ImportRules.kt deleted file mode 100644 index 650e3a34f..000000000 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_settings/ImportRules.kt +++ /dev/null @@ -1,451 +0,0 @@ -/* - This file is part of InviZible Pro. - - InviZible Pro is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - InviZible Pro is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with InviZible Pro. If not, see . - - Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com - */ - -package pan.alexander.tordnscrypt.settings.dnscrypt_settings - -import android.content.Context -import android.net.Uri -import pan.alexander.tordnscrypt.App -import pan.alexander.tordnscrypt.modules.ModulesRestarter -import pan.alexander.tordnscrypt.modules.ModulesStatus -import pan.alexander.tordnscrypt.settings.PathVars -import pan.alexander.tordnscrypt.utils.Constants.META_ADDRESS -import pan.alexander.tordnscrypt.utils.wakelock.WakeLocksManager -import pan.alexander.tordnscrypt.utils.enums.DNSCryptRulesVariant -import pan.alexander.tordnscrypt.utils.enums.ModuleState -import pan.alexander.tordnscrypt.utils.logger.Logger.loge -import java.io.BufferedReader -import java.io.File -import java.io.InputStreamReader -import java.io.PrintWriter -import java.util.* -import java.util.concurrent.locks.ReentrantLock -import kotlin.collections.ArrayList - -private val blackListHostRulesRegex = Regex("^[a-zA-Z\\d-.=_*\\[\\]?,]+$") -private val blacklistIPRulesRegex = Regex("^[0-9a-fA-F:.=*\\[\\]]+$") -private val cloakingRulesRegex = Regex("^[a-zA-Z\\d-.=_*]+[ \\t]+[a-zA-Z\\d-.=_*:]+$") -private val forwardingRulesRegex = - Regex("^[a-zA-Z\\d-._]+[ \\t]+[0-9a-fA-F:.,\\[\\]]+$") -private val whiteListHostRulesRegex = Regex("^[a-zA-Z\\d-.=_*\\[\\]?]+$") -private val hostFileRegex = Regex("^(?:0.0.0.0|127.0.0.1)[ \\t]+[a-zA-Z\\d-._]+$") -private const val itpdRedirectAddress = "*i2p 10.191.0.1" -private val excludeFromHost = listOf("localhost", "localhost.localdomain", "local", META_ADDRESS) -private val reentrantLock = ReentrantLock() -private val wakeLocksManager = WakeLocksManager.getInstance() - -class ImportRules( - private val context: Context, - private var rulesVariant: DNSCryptRulesVariant, - private val localRules: Boolean, - private val filePathToImport: Array<*> -) : Thread() { - - private val pathVars: PathVars = App.instance.daggerComponent.getPathVars().get() - - private val blackListHostRulesPath = pathVars.dnsCryptBlackListPath - private val blackListHostRulesLocalPath = pathVars.dnsCryptLocalBlackListPath - private val blackListHostRulesRemotePath = pathVars.dnsCryptRemoteBlackListPath - - private val blackListIPRulesPath = pathVars.dnsCryptIPBlackListPath - private val blackListIPRulesLocalPath = pathVars.dnsCryptLocalIPBlackListPath - private val blackListIPRulesRemotePath = pathVars.dnsCryptRemoteIPBlackListPath - - private val whiteListHostRulesPath = pathVars.dnsCryptWhiteListPath - private val whiteListHostRulesLocalPath = pathVars.dnsCryptLocalWhiteListPath - private val whiteListHostRulesRemotePath = pathVars.dnsCryptRemoteWhiteListPath - - private val cloakingRulesPath = pathVars.dnsCryptCloakingRulesPath - private val cloakingRulesLocalPath = pathVars.dnsCryptLocalCloakingRulesPath - private val cloakingRulesRemotePath = pathVars.dnsCryptRemoteCloakingRulesPath - - private val forwardingRulesPath = pathVars.dnsCryptForwardingRulesPath - private val forwardingRulesLocalPath = pathVars.dnsCryptLocalForwardingRulesPath - private val forwardingRulesRemotePath = pathVars.dnsCryptRemoteForwardingRulesPath - - private var onDNSCryptRuleAddLineListener: OnDNSCryptRuleAddLineListener? = null - - private var powerLocked = false - - private var blackListFileIsHost = false - - private val contentResolver = context.applicationContext.contentResolver - - @Volatile - private var linesCount = 0 - private var hash = 0 - private var hashes = IntArray(0) - private var savedTime = System.currentTimeMillis() - - interface OnDNSCryptRuleAddLineListener { - fun onDNSCryptRuleLinesAddingStarted(importThread: Thread) - fun onDNSCryptRuleLineAdded(count: Int) - fun onDNSCryptRuleLinesAddingFinished() - } - - fun setOnDNSCryptRuleAddLineListener(onDNSCryptRuleAddLineListener: OnDNSCryptRuleAddLineListener) { - this.onDNSCryptRuleAddLineListener = onDNSCryptRuleAddLineListener - } - - override fun run() { - - when (rulesVariant) { - DNSCryptRulesVariant.BLACKLIST_HOSTS -> doTheJob( - blackListHostRulesPath, - blackListHostRulesLocalPath, - blackListHostRulesRemotePath, - blackListHostRulesRegex, - filePathToImport - ) - - DNSCryptRulesVariant.WHITELIST_HOSTS -> doTheJob( - whiteListHostRulesPath, - whiteListHostRulesLocalPath, - whiteListHostRulesRemotePath, - whiteListHostRulesRegex, - filePathToImport - ) - - DNSCryptRulesVariant.BLACKLIST_IPS -> doTheJob( - blackListIPRulesPath, - blackListIPRulesLocalPath, - blackListIPRulesRemotePath, - blacklistIPRulesRegex, - filePathToImport - ) - - DNSCryptRulesVariant.CLOAKING -> doTheJob( - cloakingRulesPath, - cloakingRulesLocalPath, - cloakingRulesRemotePath, - cloakingRulesRegex, - filePathToImport - ) - - DNSCryptRulesVariant.FORWARDING -> doTheJob( - forwardingRulesPath, - forwardingRulesLocalPath, - forwardingRulesRemotePath, - forwardingRulesRegex, - filePathToImport - ) - - DNSCryptRulesVariant.UNDEFINED -> return - } - - } - - private fun doTheJob( - rulesFilePath: String, - localRulesFilePath: String, - remoteRulesFilePath: String, - rulesRegex: Regex, - filesToImport: Array<*> - ) { - - - reentrantLock.lock() - - if (!wakeLocksManager.isPowerWakeLockHeld) { - wakeLocksManager.managePowerWakelock(context, true) - powerLocked = true - } - - onDNSCryptRuleAddLineListener?.onDNSCryptRuleLinesAddingStarted(currentThread()) - - try { - if (filesToImport.isNotEmpty()) { - File(rulesFilePath).printWriter().use { - addDefaultLinesIfRequired(it) - mixFiles( - it, - localRulesFilePath, - remoteRulesFilePath, - rulesRegex, - filesToImport.toMutableList() - ) - } - } - - val fileToSave = if (localRules) { - localRulesFilePath - } else { - remoteRulesFilePath - } - - try { - File(rulesFilePath).copyTo(File(fileToSave), true) - } catch (e: Exception) { - onDNSCryptRuleAddLineListener?.onDNSCryptRuleLineAdded(0) - loge("ImportRules doTheJob copy", e) - } - - } catch (e: Exception) { - loge("ImportRules doTheJob", e) - } finally { - onDNSCryptRuleAddLineListener?.onDNSCryptRuleLinesAddingFinished() - - if (powerLocked) { - wakeLocksManager.stopPowerWakelock() - } - - if (linesCount > 0) { - restartDNSCryptIfRequired() - } - - reentrantLock.unlock() - } - - } - - private fun mixFiles( - printWriter: PrintWriter, - localRulesFilePath: String, - remoteRulesFilePath: String, - rulesRegex: Regex, - filesToImport: MutableList - ) = try { - - val fileToAdd: String = if (localRules) { - remoteRulesFilePath - } else { - localRulesFilePath - } - - val addFile = File(fileToAdd) - if (addFile.isFile) { - filesToImport.add(addFile) - } - - val hashIsRequired = filesToImport.size > 1 - - filesToImport.forEachIndexed { index, file -> - - if (file is String) { - mixFilesWithPass(index, file, rulesRegex, hashIsRequired, printWriter) - } else if (file is Uri) { - mixFilesWithUri(index, file, rulesRegex, hashIsRequired, printWriter) - } - } - - onDNSCryptRuleAddLineListener?.onDNSCryptRuleLineAdded(linesCount) - } catch (e: Exception) { - loge("ImportRules mixFiles", e) - } - - private fun mixFilesWithPass( - index: Int, file: String, - rulesRegex: Regex, hashIsRequired: Boolean, printWriter: PrintWriter - ) { - - if (file.isNotEmpty()) { - val inputFile = File(file) - - if (inputFile.isFile) { - try { - if (DNSCryptRulesVariant.BLACKLIST_HOSTS == rulesVariant) { - blackListFileIsHost = isInputFileFormatCorrect(inputFile, hostFileRegex) - } - - val hashesNew = ArrayList() - - if (blackListFileIsHost || isInputFileFormatCorrect(inputFile, rulesRegex)) { - inputFile.bufferedReader().use { reader -> - mixFilesCommonPart( - printWriter, - reader, - rulesRegex, - hashIsRequired, - index, - hashesNew - ) - } - } - } catch (e: Exception) { - loge("ImportRules mixFilesWithPass", e) - } - } - } - } - - private fun mixFilesWithUri( - index: Int, uri: Uri, - rulesRegex: Regex, hashIsRequired: Boolean, printWriter: PrintWriter - ) { - try { - if (DNSCryptRulesVariant.BLACKLIST_HOSTS == rulesVariant) { - blackListFileIsHost = isInputFileFormatCorrect(uri, hostFileRegex) - } - - val hashesNew = ArrayList() - - if (blackListFileIsHost || isInputFileFormatCorrect(uri, rulesRegex)) { - contentResolver.openInputStream(uri)?.use { inputStream -> - BufferedReader(InputStreamReader(inputStream)).use { reader -> - mixFilesCommonPart( - printWriter, - reader, - rulesRegex, - hashIsRequired, - index, - hashesNew - ) - } - } - } - } catch (e: Exception) { - loge("ImportRules mixFilesWithUri", e) - } - } - - private fun mixFilesCommonPart( - printWriter: PrintWriter, reader: BufferedReader, - rulesRegex: Regex, hashIsRequired: Boolean, - index: Int, hashesNew: ArrayList - ) { - var line = reader.readLine()?.trim() - while (line != null && !currentThread().isInterrupted) { - val lineReady = if (blackListFileIsHost) { - hostToBlackList(line) - } else { - cleanRule(line, rulesRegex) - } - - if (hashIsRequired) { - hash = lineReady.hashCode() - } - - if (lineReady.isNotEmpty() && (!hashIsRequired || index < 1 || Arrays.binarySearch( - hashes, - hash - ) < 0) - ) { - - if (hashIsRequired) { - hashesNew += hash - } - - printWriter.println(lineReady) - linesCount++ - if (System.currentTimeMillis() - savedTime > 500) { - onDNSCryptRuleAddLineListener?.onDNSCryptRuleLineAdded(linesCount) - savedTime = System.currentTimeMillis() - } - } - line = reader.readLine()?.trim() - } - - val arrNew = IntArray(hashes.size + hashesNew.size) - System.arraycopy(hashes, 0, arrNew, 0, hashes.size) - System.arraycopy(hashesNew.toIntArray(), 0, arrNew, hashes.size, hashesNew.size) - hashes = arrNew - hashes.sort() - } - - private fun cleanRule(line: String, regExp: Regex): String { - - if (line.startsWith("#") || !line.matches(regExp)) { - return "" - } - - return line - } - - private fun hostToBlackList(line: String): String { - var output = "" - - if (line.startsWith("#") || !line.matches(hostFileRegex)) { - return "" - } - - val index = line.lastIndexOf(" ") + 1 - if (index in 8 until line.length) { - output = line.substring(index) - } - - if (excludeFromHost.contains(output)) { - return "" - } - - return output - } - - private fun addDefaultLinesIfRequired(printWriter: PrintWriter) { - if (DNSCryptRulesVariant.CLOAKING == rulesVariant) { - printWriter.println(itpdRedirectAddress) - } else if (DNSCryptRulesVariant.FORWARDING == rulesVariant) { - printWriter.println( - "onion 127.0.0.1:" + App.instance.daggerComponent.getPathVars().get().torDNSPort - ) - } - } - - private fun isInputFileFormatCorrect(file: File, regExp: Regex): Boolean { - file.bufferedReader().use { - var line = it.readLine()?.trim() - while (line != null) { - - if (currentThread().isInterrupted) { - return false - } - - if (line.isNotEmpty() && !line.contains("#") && !line.contains("!")) { - return line.matches(regExp) - } - - line = it.readLine()?.trim() - } - } - return false - } - - private fun isInputFileFormatCorrect(uri: Uri, regExp: Regex): Boolean { - contentResolver.openInputStream(uri)?.use { inputStream -> - BufferedReader(InputStreamReader(inputStream)).use { reader -> - var line: String? = reader.readLine().trim() - var index = 0 - while (line != null) { - - if (currentThread().isInterrupted) { - return false - } - - if (line.isNotEmpty() && !line.contains("#") && !line.contains("!")) { - index++ - if (line.matches(regExp)) { - return true - } else if (index > 100) { - return false - } - } - - line = reader.readLine().trim() - } - } - } - - return false - } - - private fun restartDNSCryptIfRequired() { - if (ModulesStatus.getInstance().dnsCryptState == ModuleState.RUNNING) { - ModulesRestarter.restartDNSCrypt(context) - } - } -} \ No newline at end of file diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_settings/PreferencesDNSFragment.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_settings/PreferencesDNSFragment.java index 593fb9866..3d3ba94c4 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_settings/PreferencesDNSFragment.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_settings/PreferencesDNSFragment.java @@ -21,27 +21,19 @@ import android.app.Activity; import android.content.Context; -import android.content.Intent; import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.os.Build; import android.os.Bundle; -import android.os.Environment; +import android.os.Handler; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; import androidx.preference.Preference; import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceScreen; -import com.github.angads25.filepicker.model.DialogConfigs; -import com.github.angads25.filepicker.model.DialogProperties; -import com.github.angads25.filepicker.view.FilePickerDialog; - -import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; @@ -51,20 +43,20 @@ import dagger.Lazy; import pan.alexander.tordnscrypt.App; import pan.alexander.tordnscrypt.R; +import pan.alexander.tordnscrypt.dialogs.NotificationDialogFragment; import pan.alexander.tordnscrypt.settings.SettingsActivity; -import pan.alexander.tordnscrypt.dialogs.progressDialogs.ImportRulesDialog; import pan.alexander.tordnscrypt.modules.ModulesAux; import pan.alexander.tordnscrypt.modules.ModulesRestarter; import pan.alexander.tordnscrypt.modules.ModulesStatus; import pan.alexander.tordnscrypt.settings.ConfigEditorFragment; import pan.alexander.tordnscrypt.settings.PathVars; -import pan.alexander.tordnscrypt.utils.Utils; -import pan.alexander.tordnscrypt.utils.enums.DNSCryptRulesVariant; +import pan.alexander.tordnscrypt.domain.dns_rules.DnsRuleType; +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteRulesWorkManager; +import pan.alexander.tordnscrypt.utils.enums.ModuleState; import pan.alexander.tordnscrypt.utils.executors.CoroutineExecutor; import pan.alexander.tordnscrypt.utils.filemanager.FileManager; import pan.alexander.tordnscrypt.vpn.service.VpnBuilder; -import static android.provider.DocumentsContract.EXTRA_INITIAL_URI; import static pan.alexander.tordnscrypt.assistance.AccelerateDevelop.accelerated; import static pan.alexander.tordnscrypt.di.SharedPreferencesModule.DEFAULT_PREFERENCES_NAME; import static pan.alexander.tordnscrypt.utils.Constants.DNSCRYPT_RELAYS_SOURCE_IPV6; @@ -85,6 +77,9 @@ import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.DNSCRYPT_LISTEN_PORT; import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.DNSCRYPT_NETPROBE_ADDRESS; import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.DNSCRYPT_OUTBOUND_PROXY; +import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.DNSCRYPT_RELAYS_REFRESH_DELAY; +import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.DNSCRYPT_RULES_REFRESH_DELAY; +import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.DNSCRYPT_SERVERS_REFRESH_DELAY; import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.HTTP3_QUIC; import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.IGNORE_SYSTEM_DNS; import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPED; @@ -95,13 +90,7 @@ public class PreferencesDNSFragment extends PreferenceFragmentCompat implements Preference.OnPreferenceChangeListener, - Preference.OnPreferenceClickListener { - - public static final int PICK_BLACKLIST_HOSTS = 1001; - public static final int PICK_WHITELIST_HOSTS = 1002; - public static final int PICK_BLACKLIST_IPS = 1003; - public static final int PICK_FORWARDING = 1004; - public static final int PICK_CLOAKING = 1005; + Preference.OnPreferenceClickListener, RulesEraser.OnRulesErased { @Inject public Lazy pathVars; @@ -110,6 +99,12 @@ public class PreferencesDNSFragment extends PreferenceFragmentCompat @Inject @Named(DEFAULT_PREFERENCES_NAME) public SharedPreferences defaultPreferences; + @Inject + public Lazy handler; + @Inject + public Lazy rulesEraser; + @Inject + public Lazy updateRemoteRulesWorkManager; private ArrayList key_toml; private ArrayList val_toml; @@ -117,7 +112,6 @@ public class PreferencesDNSFragment extends PreferenceFragmentCompat private ArrayList val_toml_orig; private String appDataDir; private boolean isChanged = false; - private boolean rootDirAccessible; @SuppressWarnings("deprecation") @Override @@ -128,8 +122,6 @@ public void onCreate(Bundle savedInstanceState) { addPreferencesFromResource(R.xml.preferences_dnscrypt); - checkRootDirAccessible(); - if (pathVars.get().getAppVersion().endsWith("p")) { removePreferencesWithGPVersion(); } @@ -155,9 +147,10 @@ public void onCreate(Bundle savedInstanceState) { preferences.add(findPreference("ignored_qtypes")); preferences.add(findPreference("Enable Suspicious logging")); preferences.add(findPreference("Sources")); - preferences.add(findPreference("refresh_delay")); + preferences.add(findPreference(DNSCRYPT_SERVERS_REFRESH_DELAY)); + preferences.add(findPreference(DNSCRYPT_RULES_REFRESH_DELAY)); preferences.add(findPreference("Relays")); - preferences.add(findPreference("refresh_delay_relays")); + preferences.add(findPreference(DNSCRYPT_RELAYS_REFRESH_DELAY)); preferences.add(findPreference("block_unqualified")); preferences.add(findPreference("block_undelegated")); preferences.add(findPreference(DNSCRYPT_BLOCK_IPv6)); @@ -185,7 +178,7 @@ public void onCreate(Bundle savedInstanceState) { } if (ModulesStatus.getInstance().isUseModulesWithRoot()) { - removeImportErasePrefs(); + removeErasePrefs(); } else { registerImportErasePrefs(); } @@ -194,11 +187,6 @@ public void onCreate(Bundle savedInstanceState) { private void registerImportErasePrefs() { ArrayList preferences = new ArrayList<>(); - preferences.add(findPreference("local_blacklist")); - preferences.add(findPreference("local_whitelist")); - preferences.add(findPreference("local_ipblacklist")); - preferences.add(findPreference("local_forwarding_rules")); - preferences.add(findPreference("local_cloaking_rules")); preferences.add(findPreference("erase_blacklist")); preferences.add(findPreference("erase_whitelist")); preferences.add(findPreference("erase_ipblacklist")); @@ -214,44 +202,34 @@ private void registerImportErasePrefs() { } } - private void removeImportErasePrefs() { + private void removeErasePrefs() { PreferenceCategory forwarding = findPreference("pref_dnscrypt_forwarding_rules"); - Preference local_forwarding_rules = findPreference("local_forwarding_rules"); Preference erase_forwarding_rules = findPreference("erase_forwarding_rules"); - if (forwarding != null && local_forwarding_rules != null && erase_forwarding_rules != null) { - forwarding.removePreference(local_forwarding_rules); + if (forwarding != null && erase_forwarding_rules != null) { forwarding.removePreference(erase_forwarding_rules); } PreferenceCategory cloaking = findPreference("pref_dnscrypt_cloaking_rules"); - Preference local_cloaking_rules = findPreference("local_cloaking_rules"); Preference erase_cloaking_rules = findPreference("erase_cloaking_rules"); - if (cloaking != null && local_cloaking_rules != null && erase_cloaking_rules != null) { - cloaking.removePreference(local_cloaking_rules); + if (cloaking != null && erase_cloaking_rules != null) { cloaking.removePreference(erase_cloaking_rules); } PreferenceCategory blacklist = findPreference("pref_dnscrypt_blacklist"); - Preference local_blacklist = findPreference("local_blacklist"); Preference erase_blacklist = findPreference("erase_blacklist"); - if (blacklist != null && local_blacklist != null && erase_blacklist != null) { - blacklist.removePreference(local_blacklist); + if (blacklist != null && erase_blacklist != null) { blacklist.removePreference(erase_blacklist); } PreferenceCategory ipblacklist = findPreference("pref_dnscrypt_ipblacklist"); - Preference local_ipblacklist = findPreference("local_ipblacklist"); Preference erase_ipblacklist = findPreference("erase_ipblacklist"); - if (ipblacklist != null && local_ipblacklist != null && erase_ipblacklist != null) { - ipblacklist.removePreference(local_ipblacklist); + if (ipblacklist != null && erase_ipblacklist != null) { ipblacklist.removePreference(erase_ipblacklist); } PreferenceCategory whitelist = findPreference("pref_dnscrypt_whitelist"); - Preference local_whitelist = findPreference("local_whitelist"); Preference erase_whitelist = findPreference("erase_whitelist"); - if (whitelist != null && local_whitelist != null && erase_whitelist != null) { - whitelist.removePreference(local_whitelist); + if (whitelist != null && erase_whitelist != null) { whitelist.removePreference(erase_whitelist); } } @@ -402,16 +380,22 @@ public boolean onPreferenceChange(@NonNull Preference preference, Object newValu } val_toml.set(key_toml.lastIndexOf("urls"), newValue.toString()); return true; - } else if (Objects.equals(preference.getKey(), "refresh_delay")) { + } else if (Objects.equals(preference.getKey(), DNSCRYPT_SERVERS_REFRESH_DELAY)) { if (!newValue.toString().matches("\\d+")) { return false; } - } else if (Objects.equals(preference.getKey(), "refresh_delay_relays")) { + } else if (Objects.equals(preference.getKey(), DNSCRYPT_RELAYS_REFRESH_DELAY)) { if (!newValue.toString().matches("\\d+")) { return false; } val_toml.set(key_toml.lastIndexOf("refresh_delay"), newValue.toString()); return true; + } else if (Objects.equals(preference.getKey(), DNSCRYPT_RULES_REFRESH_DELAY)) { + if (!newValue.toString().matches("\\d+")) { + return false; + } + refreshDnsCryptRulesUpdateInterval(Long.parseLong(newValue.toString())); + return true; } else if (Objects.equals(preference.getKey(), DNSCRYPT_OUTBOUND_PROXY)) { if (Boolean.parseBoolean(newValue.toString()) && key_toml.contains("#proxy") && key_toml.contains("force_tcp")) { @@ -558,6 +542,10 @@ public boolean onPreferenceChange(@NonNull Preference preference, Object newValu return false; } + private void refreshDnsCryptRulesUpdateInterval(long delay) { + updateRemoteRulesWorkManager.get().updateRefreshDnsRulesInterval(delay); + } + private List getDNSCryptBootstrapResolvers(String newValue) { List resolvers = new ArrayList<>(); for (String resolver : newValue.split(", ?")) { @@ -746,78 +734,23 @@ public boolean onPreferenceClick(@NonNull Preference preference) { return true; } else if (Objects.equals(preference.getKey().trim(), "erase_blacklist")) { showAreYouSureDialog(activity, R.string.pref_dnscrypt_erase_blacklist, () -> - eraseRules(activity, DNSCryptRulesVariant.BLACKLIST_HOSTS, "remote_blacklist")); + eraseRules(DnsRuleType.BLACKLIST)); return true; } else if (Objects.equals(preference.getKey().trim(), "erase_whitelist")) { showAreYouSureDialog(activity, R.string.pref_dnscrypt_erase_whitelist, () -> - eraseRules(activity, DNSCryptRulesVariant.WHITELIST_HOSTS, "remote_whitelist")); + eraseRules(DnsRuleType.WHITELIST)); return true; } else if (Objects.equals(preference.getKey().trim(), "erase_ipblacklist")) { showAreYouSureDialog(activity, R.string.pref_dnscrypt_erase_ipblacklist, () -> - eraseRules(activity, DNSCryptRulesVariant.BLACKLIST_IPS, "remote_ipblacklist")); + eraseRules(DnsRuleType.IP_BLACKLIST)); return true; } else if (Objects.equals(preference.getKey().trim(), "erase_forwarding_rules")) { showAreYouSureDialog(activity, R.string.pref_dnscrypt_erase_forwarding_rules, () -> - eraseRules(activity, DNSCryptRulesVariant.FORWARDING, "remote_forwarding_rules")); + eraseRules(DnsRuleType.FORWARDING)); return true; } else if (Objects.equals(preference.getKey().trim(), "erase_cloaking_rules")) { showAreYouSureDialog(activity, R.string.pref_dnscrypt_erase_cloaking_rules, () -> - eraseRules(activity, DNSCryptRulesVariant.CLOAKING, "remote_cloaking_rules")); - return true; - } else if (Objects.equals(preference.getKey().trim(), "local_blacklist")) { - - if (rootDirAccessible) { - FilePickerDialog dialog = new FilePickerDialog(activity, getFilePickerProperties(activity)); - dialog.setDialogSelectionListener(files -> importRules(activity, DNSCryptRulesVariant.BLACKLIST_HOSTS, files)); - dialog.show(); - } else { - openFileWithSAF(activity, PICK_BLACKLIST_HOSTS); - } - - return true; - } else if (Objects.equals(preference.getKey().trim(), "local_whitelist")) { - - if (rootDirAccessible) { - FilePickerDialog dialog = new FilePickerDialog(activity, getFilePickerProperties(activity)); - dialog.setDialogSelectionListener(files -> importRules(activity, DNSCryptRulesVariant.WHITELIST_HOSTS, files)); - dialog.show(); - } else { - openFileWithSAF(activity, PICK_WHITELIST_HOSTS); - } - - return true; - } else if (Objects.equals(preference.getKey().trim(), "local_ipblacklist")) { - - if (rootDirAccessible) { - FilePickerDialog dialog = new FilePickerDialog(activity, getFilePickerProperties(activity)); - dialog.setDialogSelectionListener(files -> importRules(activity, DNSCryptRulesVariant.BLACKLIST_IPS, files)); - dialog.show(); - } else { - openFileWithSAF(activity, PICK_BLACKLIST_IPS); - } - - return true; - } else if (Objects.equals(preference.getKey().trim(), "local_forwarding_rules")) { - - if (rootDirAccessible) { - FilePickerDialog dialog = new FilePickerDialog(activity, getFilePickerProperties(activity)); - dialog.setDialogSelectionListener(files -> importRules(activity, DNSCryptRulesVariant.FORWARDING, files)); - dialog.show(); - } else { - openFileWithSAF(activity, PICK_FORWARDING); - } - - return true; - } else if (Objects.equals(preference.getKey().trim(), "local_cloaking_rules")) { - - if (rootDirAccessible) { - FilePickerDialog dialog = new FilePickerDialog(activity, getFilePickerProperties(activity)); - dialog.setDialogSelectionListener(files -> importRules(activity, DNSCryptRulesVariant.CLOAKING, files)); - dialog.show(); - } else { - openFileWithSAF(activity, PICK_CLOAKING); - } - + eraseRules(DnsRuleType.CLOAKING)); return true; } else if ("cleanDNSCryptFolder".equals(preference.getKey().trim())) { cleanModuleFolder(activity); @@ -826,28 +759,36 @@ public boolean onPreferenceClick(@NonNull Preference preference) { return false; } - public void importRules(Context context, DNSCryptRulesVariant dnsCryptRulesVariant, Object[] files) { + private void eraseRules(DnsRuleType ruleType) { + RulesEraser eraser = rulesEraser.get(); + eraser.setCallback(this); + executor.submit("PreferencesDNSFragment eraseRules", () -> { + eraser.eraseRules(ruleType); + return null; + }); + } - if (context == null) { - return; - } + @Override + public void onRulesEraseFinished() { + showRulesErasedDialog(); + restartDnsCryptIfNeeded(); + } - ImportRules importRules = new ImportRules(context, dnsCryptRulesVariant, - true, files); - ImportRulesDialog importRulesDialog = ImportRulesDialog.newInstance(); - importRules.setOnDNSCryptRuleAddLineListener(importRulesDialog); - importRulesDialog.show(getParentFragmentManager(), "ImportRulesDialog"); - importRules.start(); + private void showRulesErasedDialog() { + DialogFragment dialog = NotificationDialogFragment.newInstance(R.string.erase_dnscrypt_rules_dialog_message); + if (handler != null) { + handler.get().post(() -> dialog.show(getParentFragmentManager(), "EraseDialog")); + } } - private void eraseRules(Context context, DNSCryptRulesVariant dnsCryptRulesVariant, String remoteRulesLinkPreferenceTag) { + private void restartDnsCryptIfNeeded() { + Context context = getContext(); if (context == null) { return; } - - EraseRules eraseRules = new EraseRules(context, getParentFragmentManager(), - dnsCryptRulesVariant, remoteRulesLinkPreferenceTag); - eraseRules.start(); + if (ModulesStatus.getInstance().getDnsCryptState() == ModuleState.RUNNING) { + ModulesRestarter.restartDNSCrypt(context); + } } private void showAreYouSureDialog(Activity activity, int title, Runnable action) { @@ -859,29 +800,6 @@ private void showAreYouSureDialog(Activity activity, int title, Runnable action) builder.show(); } - private void openFileWithSAF(Activity activity, int fileType) { - if (activity == null) { - return; - } - - Uri uri = Uri.fromFile(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)); - - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setType("text/plain"); - intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); - - if (uri != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - intent.putExtra(EXTRA_INITIAL_URI, uri); - } - - PackageManager packageManager = activity.getPackageManager(); - if (packageManager != null && intent.resolveActivity(packageManager) != null) { - activity.startActivityForResult(intent, fileType); - } - - } - private void removePreferencesWithGPVersion() { PreferenceScreen dnscryptSettings = findPreference("dnscrypt_settings"); @@ -891,6 +809,7 @@ private void removePreferencesWithGPVersion() { categories.add(findPreference("pref_dnscrypt_blacklist")); categories.add(findPreference("pref_dnscrypt_ipblacklist")); categories.add(findPreference("pref_dnscrypt_whitelist")); + categories.add(findPreference("pref_dnscrypt_refresh_rules")); for (PreferenceCategory category : categories) { if (dnscryptSettings != null && category != null) { @@ -934,33 +853,6 @@ private void removePreferencesWithGPVersion() { } } - private void checkRootDirAccessible() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - executor.submit( - "PreferencesDNSFragment checkRootDirAccessible", - () -> { - rootDirAccessible = Utils.INSTANCE.isLogsDirAccessible(); - return null; - } - ); - } - } - - private DialogProperties getFilePickerProperties(Context context) { - - String cacheDirPath = pathVars.get().getCacheDirPath(context); - - DialogProperties properties = new DialogProperties(); - properties.selection_mode = DialogConfigs.MULTI_MODE; - properties.selection_type = DialogConfigs.FILE_SELECT; - properties.root = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); - properties.error_dir = new File(cacheDirPath); - properties.offset = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); - properties.extensions = new String[]{"txt"}; - - return properties; - } - private void cleanModuleFolder(Context context) { if (ModulesStatus.getInstance().getDnsCryptState() != STOPPED) { Toast.makeText(context, R.string.btnDNSCryptStop, Toast.LENGTH_SHORT).show(); diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_settings/RulesEraser.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_settings/RulesEraser.kt new file mode 100644 index 000000000..3df596264 --- /dev/null +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_settings/RulesEraser.kt @@ -0,0 +1,144 @@ +/* + This file is part of InviZible Pro. + + InviZible Pro is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + InviZible Pro is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with InviZible Pro. If not, see . + + Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com + */ + +package pan.alexander.tordnscrypt.settings.dnscrypt_settings + +import androidx.annotation.WorkerThread +import pan.alexander.tordnscrypt.domain.preferences.PreferenceRepository +import pan.alexander.tordnscrypt.domain.dns_rules.DnsRuleType +import pan.alexander.tordnscrypt.settings.PathVars +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.existing.RemixExistingRulesWorkManager +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.UpdateLocalRulesWorkManager +import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteRulesWorkManager +import pan.alexander.tordnscrypt.utils.logger.Logger.loge +import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REMOTE_BLACKLIST_URL +import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REMOTE_CLOAKING_URL +import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REMOTE_FORWARDING_URL +import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REMOTE_IP_BLACKLIST_URL +import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REMOTE_WHITELIST_URL +import java.io.File +import javax.inject.Inject +import kotlin.Exception + +class RulesEraser @Inject constructor( + private val preferences: PreferenceRepository, + private val pathVars: PathVars, + private val remixExistingRulesWorkManager: RemixExistingRulesWorkManager, + private val updateRemoteDnsRulesManager: UpdateRemoteRulesWorkManager, + private val updateLocalRulesWorkManager: UpdateLocalRulesWorkManager +) { + + var callback: OnRulesErased? = null + + @WorkerThread + fun eraseRules(ruleType: DnsRuleType) { + stopRelatedWorks(ruleType) + Thread.sleep(500) + getFiles(ruleType).forEach { + eraseFile(it) + } + erasePreference(ruleType) + callback?.onRulesEraseFinished() + } + + private fun getFiles(ruleType: DnsRuleType) = + when (ruleType) { + DnsRuleType.BLACKLIST -> listOf( + pathVars.dnsCryptBlackListPath, + pathVars.dnsCryptSingleBlackListPath, + pathVars.dnsCryptLocalBlackListPath, + pathVars.dnsCryptRemoteBlackListPath + ) + + DnsRuleType.IP_BLACKLIST -> listOf( + pathVars.dnsCryptIPBlackListPath, + pathVars.dnsCryptSingleIPBlackListPath, + pathVars.dnsCryptLocalIPBlackListPath, + pathVars.dnsCryptRemoteIPBlackListPath + ) + + DnsRuleType.WHITELIST -> listOf( + pathVars.dnsCryptWhiteListPath, + pathVars.dnsCryptSingleWhiteListPath, + pathVars.dnsCryptLocalWhiteListPath, + pathVars.dnsCryptRemoteWhiteListPath + ) + + DnsRuleType.CLOAKING -> listOf( + pathVars.dnsCryptCloakingRulesPath, + pathVars.dnsCryptSingleCloakingRulesPath, + pathVars.dnsCryptLocalCloakingRulesPath, + pathVars.dnsCryptRemoteCloakingRulesPath + ) + + DnsRuleType.FORWARDING -> listOf( + pathVars.dnsCryptForwardingRulesPath, + pathVars.dnsCryptSingleForwardingRulesPath, + pathVars.dnsCryptLocalForwardingRulesPath, + pathVars.dnsCryptRemoteForwardingRulesPath + ) + } + + private fun eraseFile(filePath: String) { + + var eraseText = "" + if (filePath == pathVars.dnsCryptCloakingRulesPath + || filePath == pathVars.dnsCryptSingleCloakingRulesPath + ) { + eraseText = pathVars.dnsCryptDefaultCloakingRule + } else if (filePath == pathVars.dnsCryptForwardingRulesPath + || filePath == pathVars.dnsCryptSingleForwardingRulesPath + ) { + eraseText = pathVars.dnsCryptDefaultForwardingRule + } + + try { + val file = File(filePath) + if (file.isFile) { + file.printWriter().use { it.println(eraseText) } + } + } catch (e: Exception) { + loge("EraseRules", e) + } + + } + + private fun erasePreference(ruleType: DnsRuleType) { + preferences.setStringPreference(getRemoteRulesUrlPreferenceKey(ruleType), "") + } + + private fun getRemoteRulesUrlPreferenceKey(ruleType: DnsRuleType) = + when (ruleType) { + DnsRuleType.BLACKLIST -> REMOTE_BLACKLIST_URL + DnsRuleType.IP_BLACKLIST -> REMOTE_IP_BLACKLIST_URL + DnsRuleType.WHITELIST -> REMOTE_WHITELIST_URL + DnsRuleType.CLOAKING -> REMOTE_CLOAKING_URL + DnsRuleType.FORWARDING -> REMOTE_FORWARDING_URL + } + + private fun stopRelatedWorks(ruleType: DnsRuleType) { + remixExistingRulesWorkManager.stopMix(ruleType) + updateRemoteDnsRulesManager.stopRefreshDnsRules(ruleType) + updateLocalRulesWorkManager.stopImportDnsRules(ruleType) + } + + interface OnRulesErased { + fun onRulesEraseFinished() + } +} diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/firewall/FirewallFragment.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/firewall/FirewallFragment.kt index 08bf1b3c9..e72cada79 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/firewall/FirewallFragment.kt +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/firewall/FirewallFragment.kt @@ -47,9 +47,11 @@ import pan.alexander.tordnscrypt.di.SharedPreferencesModule import pan.alexander.tordnscrypt.dialogs.NotificationHelper import pan.alexander.tordnscrypt.dialogs.NotificationHelper.TAG_HELPER import pan.alexander.tordnscrypt.domain.preferences.PreferenceRepository +import pan.alexander.tordnscrypt.modules.ModulesAux import pan.alexander.tordnscrypt.modules.ModulesStatus import pan.alexander.tordnscrypt.settings.OnBackPressListener import pan.alexander.tordnscrypt.settings.firewall.adapter.FirewallAdapter +import pan.alexander.tordnscrypt.utils.enums.ModuleState import pan.alexander.tordnscrypt.utils.enums.OperationMode import pan.alexander.tordnscrypt.utils.logger.Logger.loge import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.FIREWALL_ENABLED @@ -105,7 +107,7 @@ class FirewallFragment : Fragment(), private var searchText: String? = null - var firewallEnabled = false + private var firewallEnabled = false override fun onCreate(savedInstanceState: Bundle?) { App.instance.daggerComponent.inject(this) @@ -244,6 +246,7 @@ class FirewallFragment : Fragment(), if (firewallFirstStart) { activateAll(true) viewModel.activateAllFirsStart() + startFirewall() } when { @@ -353,7 +356,7 @@ class FirewallFragment : Fragment(), R.id.btnTopUnCheckAllFirewall -> activateAll(false) R.id.btnPowerFirewall -> { enableFirewall() - modulesStatus.setIptablesRulesUpdateRequested(context, true) + startFirewall() } else -> loge("FirewallFragment onClick unknown id: ${v.id}") } @@ -404,8 +407,10 @@ class FirewallFragment : Fragment(), if (buttonView.id == R.id.menu_switch) { if (isChecked) { enableFirewall() + startFirewall() } else { disableFirewall() + stopFirewall() } modulesStatus.setIptablesRulesUpdateRequested(context, true) } @@ -460,6 +465,20 @@ class FirewallFragment : Fragment(), binding.btnPowerFirewall.setOnClickListener(this) } + private fun startFirewall() { + if (modulesStatus.firewallState != ModuleState.RUNNING) { + modulesStatus.setFirewallState(ModuleState.STARTING, preferenceRepository.get()) + ModulesAux.makeModulesStateExtraLoop(context) + } + } + + private fun stopFirewall() { + if (modulesStatus.firewallState != ModuleState.STOPPED) { + modulesStatus.setFirewallState(ModuleState.STOPPING, preferenceRepository.get()) + ModulesAux.makeModulesStateExtraLoop(context) + } + } + private fun searchApps(text: String?) { searchText = text?.trim()?.lowercase() diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/firewall/SaveFirewallChangesDialog.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/firewall/SaveFirewallChangesDialog.kt index 35175912e..f15f861bb 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/firewall/SaveFirewallChangesDialog.kt +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/firewall/SaveFirewallChangesDialog.kt @@ -23,7 +23,6 @@ import androidx.appcompat.app.AlertDialog import pan.alexander.tordnscrypt.R import pan.alexander.tordnscrypt.dialogs.ExtendedDialogFragment import pan.alexander.tordnscrypt.modules.ModulesStatus -import pan.alexander.tordnscrypt.utils.enums.ModuleState import pan.alexander.tordnscrypt.utils.logger.Logger.loge import java.lang.Exception @@ -41,16 +40,7 @@ class SaveFirewallChangesDialog : ExtendedDialogFragment() { val firewallFragment = parentFragmentManager.findFragmentByTag(FirewallFragment.TAG) as? FirewallFragment ?: return null - val modulesRunning = modulesStatus.dnsCryptState == ModuleState.RUNNING - || modulesStatus.torState == ModuleState.RUNNING - val firewallEnabled = firewallFragment.firewallEnabled - - val message = if (!firewallEnabled || firewallEnabled && modulesRunning) { - activity.getString(R.string.ask_save_changes) - } else { - activity.getString(R.string.ask_save_changes) + "\n\t\n" + - activity.getString(R.string.firewall_warning_enable_module) - } + val message = activity.getString(R.string.ask_save_changes) val builder = AlertDialog.Builder(activity) diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/firewall/adapter/FirewallAdapter.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/firewall/adapter/FirewallAdapter.kt index e2788621f..3160a0d26 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/firewall/adapter/FirewallAdapter.kt +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/firewall/adapter/FirewallAdapter.kt @@ -180,15 +180,15 @@ class FirewallAdapter( private val cardAppFirewall = itemView.findViewById(R.id.cardAppFirewall) private val imgAppIconFirewall = itemView.findViewById(R.id.imgAppIconFirewall) private val btnLanFirewall = itemView.findViewById(R.id.btnLanFirewall) - .also { it.setOnClickListener(this) } + ?.also { it.setOnClickListener(this) } private val btnWifiFirewall = itemView.findViewById(R.id.btnWifiFirewall) - .also { it.setOnClickListener(this) } + ?.also { it.setOnClickListener(this) } private val btnGsmFirewall = itemView.findViewById(R.id.btnGsmFirewall) - .also { it.setOnClickListener(this) } + ?.also { it.setOnClickListener(this) } private val btnRoamingFirewall = itemView.findViewById(R.id.btnRoamingFirewall) - .also { it.setOnClickListener(this) } + ?.also { it.setOnClickListener(this) } private val btnVpnFirewall = itemView.findViewById(R.id.btnVpnFirewall) - .also { it.setOnClickListener(this) } + ?.also { it.setOnClickListener(this) } private val tvAppName = itemView.findViewById(R.id.tvAppName) fun bind(position: Int) { diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/itpd_settings/ITPDSubscriptionsFragment.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/itpd_settings/ITPDSubscriptionsFragment.java new file mode 100644 index 000000000..95bdc475c --- /dev/null +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/itpd_settings/ITPDSubscriptionsFragment.java @@ -0,0 +1,276 @@ +/* + This file is part of InviZible Pro. + + InviZible Pro is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + InviZible Pro is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with InviZible Pro. If not, see . + + Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com + */ + +package pan.alexander.tordnscrypt.settings.itpd_settings; + +import static androidx.recyclerview.widget.RecyclerView.NO_POSITION; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.android.material.floatingactionbutton.FloatingActionButton; + +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +import pan.alexander.tordnscrypt.App; +import pan.alexander.tordnscrypt.R; +import pan.alexander.tordnscrypt.databinding.FragmentItpdSubscriptionBinding; +import pan.alexander.tordnscrypt.databinding.ItemRulesBinding; + +import static pan.alexander.tordnscrypt.utils.logger.Logger.loge; + +import javax.inject.Inject; + + +public class ITPDSubscriptionsFragment extends Fragment implements View.OnClickListener { + + @Inject + public ViewModelProvider.Factory viewModelFactory; + + private ItpdSubscriptionsViewModel viewModel; + + private FragmentItpdSubscriptionBinding binding; + + private SubscriptionsAdapter adapter; + + + public ITPDSubscriptionsFragment() { + } + + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + App.getInstance().getDaggerComponent().inject(this); + super.onCreate(savedInstanceState); + + viewModel = new ViewModelProvider(this, viewModelFactory).get(ItpdSubscriptionsViewModel.class); + + Activity activity = getActivity(); + if (activity == null) { + return; + } + + setTitle(activity); + } + + private void setTitle(Activity activity) { + activity.setTitle(R.string.pref_itpd_addressbook_subscriptions); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + try { + binding = FragmentItpdSubscriptionBinding.inflate(inflater, container, false); + } catch (Exception e) { + loge("ShowRulesRecycleFrag onCreateView", e); + throw e; + } + + initRecycler(); + initFab(); + + return binding.getRoot(); + } + + private void initRecycler() { + RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(requireContext()); + binding.rvRules.setLayoutManager(mLayoutManager); + adapter = new SubscriptionsAdapter(); + binding.rvRules.setAdapter(adapter); + } + + private void initFab() { + FloatingActionButton btnAddRule = binding.floatingBtnAddRule; + btnAddRule.setAlpha(0.8f); + btnAddRule.setOnClickListener(this); + btnAddRule.requestFocus(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + observeSubscriptions(); + requestSubscriptions(); + } + + private void observeSubscriptions() { + viewModel.getSubscriptions().observe(getViewLifecycleOwner(), subscriptions -> { + if (subscriptions != null) { + adapter.updateSubscriptions(subscriptions); + } + }); + } + + private void requestSubscriptions() { + viewModel.requestSubscriptions(); + } + + @Override + public void onClick(View v) { + adapter.addSubscription(); + } + + private class SubscriptionsAdapter extends RecyclerView.Adapter { + + private final List subscriptions = new ArrayList<>(); + + @SuppressLint("NotifyDataSetChanged") + void updateSubscriptions(List subscriptions) { + this.subscriptions.clear(); + for (ItpdSubscriptionRecycleItem item : subscriptions) { + this.subscriptions.add(new ItpdSubscriptionRecycleItem(item.getText())); + } + notifyDataSetChanged(); + } + + void addSubscription() { + int position = subscriptions.size(); + subscriptions.add( + position, + new ItpdSubscriptionRecycleItem("") + ); + notifyItemInserted(position); + binding.rvRules.scrollToPosition(position); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view; + try { + view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_rules, parent, false); + } catch (Exception e) { + loge("ShowRulesRecycleFrag onCreateViewHolder", e); + throw e; + } + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + holder.bind(position); + } + + @Override + public int getItemCount() { + return subscriptions.size(); + } + + ItpdSubscriptionRecycleItem getRule(int position) { + return subscriptions.get(position); + } + + void delRule(int position) { + subscriptions.remove(position); + } + + class ViewHolder extends RecyclerView.ViewHolder + implements View.OnClickListener, View.OnFocusChangeListener { + + ItemRulesBinding itemRulesBinding; + + ViewHolder(View itemView) { + super(itemView); + + itemRulesBinding = ItemRulesBinding.bind(itemView); + + itemRulesBinding.etRule.addTextChangedListener(textWatcher); + itemRulesBinding.delBtnRules.setOnClickListener(this); + } + + void bind(int position) { + itemRulesBinding.etRule.setText(getRule(position).getText(), TextView.BufferType.EDITABLE); + itemRulesBinding.swRuleActive.setVisibility(View.GONE); + } + + TextWatcher textWatcher = new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + int position = getBindingAdapterPosition(); + if (position != NO_POSITION) { + getRule(position).setText(s.toString()); + } + } + + @Override + public void afterTextChanged(Editable s) { + } + }; + + @Override + public void onClick(View v) { + if (v.getId() == R.id.delBtnRules) { + int position = getBindingAdapterPosition(); + if (position != NO_POSITION) { + delRule(position); + notifyItemRemoved(position); + } + } + } + + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (hasFocus) { + int position = getBindingAdapterPosition(); + if (position != NO_POSITION) { + binding.rvRules.smoothScrollToPosition(position); + } + } + } + } + } + + @Override + public void onStop() { + super.onStop(); + + viewModel.saveSubscriptions(requireContext(), adapter.subscriptions); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + + binding = null; + } +} diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/itpd_settings/ItpdSubscriptionRecycleItem.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/itpd_settings/ItpdSubscriptionRecycleItem.java new file mode 100644 index 000000000..53d6f4b7f --- /dev/null +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/itpd_settings/ItpdSubscriptionRecycleItem.java @@ -0,0 +1,62 @@ +/* + This file is part of InviZible Pro. + + InviZible Pro is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + InviZible Pro is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with InviZible Pro. If not, see . + + Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com + */ + +package pan.alexander.tordnscrypt.settings.itpd_settings; + +import androidx.annotation.NonNull; + +import java.util.Objects; + +public class ItpdSubscriptionRecycleItem { + + private String text; + + public ItpdSubscriptionRecycleItem(String text) { + this.text = text; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ItpdSubscriptionRecycleItem that = (ItpdSubscriptionRecycleItem) o; + return text.equals(that.text); + } + + @Override + public int hashCode() { + return Objects.hashCode(text); + } + + @NonNull + @Override + public String toString() { + return "ItpdSubscriptionRecycleItem{" + + "text='" + text + '\'' + + '}'; + } +} diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/itpd_settings/ItpdSubscriptionsViewModel.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/itpd_settings/ItpdSubscriptionsViewModel.kt new file mode 100644 index 000000000..67e2eeebe --- /dev/null +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/itpd_settings/ItpdSubscriptionsViewModel.kt @@ -0,0 +1,145 @@ +/* + This file is part of InviZible Pro. + + InviZible Pro is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + InviZible Pro is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with InviZible Pro. If not, see . + + Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com + */ + +package pan.alexander.tordnscrypt.settings.itpd_settings + +import android.content.Context +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.plus +import pan.alexander.tordnscrypt.R +import pan.alexander.tordnscrypt.di.CoroutinesModule +import pan.alexander.tordnscrypt.modules.ModulesAux +import pan.alexander.tordnscrypt.modules.ModulesRestarter +import pan.alexander.tordnscrypt.settings.PathVars +import pan.alexander.tordnscrypt.utils.Constants.URL_REGEX +import pan.alexander.tordnscrypt.utils.logger.Logger.loge +import java.io.File +import javax.inject.Inject +import javax.inject.Named + +class ItpdSubscriptionsViewModel @Inject constructor( + private val pathVars: PathVars, + @Named(CoroutinesModule.DISPATCHER_IO) + private val dispatcherIo: CoroutineDispatcher, + @Named(CoroutinesModule.SUPERVISOR_JOB_IO_DISPATCHER_SCOPE) + private val baseCoroutineScope: CoroutineScope, + coroutineExceptionHandler: CoroutineExceptionHandler +) : ViewModel() { + + private val scope: CoroutineScope = + baseCoroutineScope + CoroutineName("ItpdSubscriptionsViewModelCoroutine") + coroutineExceptionHandler + + private val subscriptionsMutable = MutableLiveData?>(null) + val subscriptions: LiveData?> get() = subscriptionsMutable + + fun requestSubscriptions() { + viewModelScope.launch(dispatcherIo) { + val itpdConf = getItpdConf() + var header = "" + for (line in itpdConf) { + if (line.startsWith("[") && line.endsWith("]")) { + header = line.trim { it == '[' || it == ']' } + } else if (header == "addressbook" && line.startsWith("subscriptions = ")) { + subscriptionsMutable.postValue( + line.removePrefix("subscriptions = ") + .split(",") + .map { ItpdSubscriptionRecycleItem(it.trim()) } + ) + break + } + } + } + } + + fun saveSubscriptions(context: Context, subscriptions: List) { + scope.launch { + + val savedSubscriptions = this@ItpdSubscriptionsViewModel.subscriptions.value + + if (subscriptions.size == savedSubscriptions?.size + && subscriptions.containsAll(savedSubscriptions) + ) { + return@launch + } + + val subscriptionLine = subscriptions.toSet().filter { + it.text.matches(Regex(URL_REGEX)) + }.takeIf { + it.isNotEmpty() + }?.joinToString(", ") { + it.text + } ?: context.resources.getStringArray(R.array.default_itpd_subscriptions) + .joinToString(", ") + + val itpdConf = getItpdConf().toMutableList() + if (itpdConf.isEmpty()) { + return@launch + } + + var header = "" + for (i in itpdConf.indices) { + val line = itpdConf[i] + if (line.startsWith("[") && line.endsWith("]")) { + header = line.trim { it == '[' || it == ']' } + } else if (header == "addressbook" && line.startsWith("subscriptions = ")) { + itpdConf[i] = "subscriptions = $subscriptionLine" + if (line != itpdConf[i]) { + break + } else { + return@launch + } + } + } + + saveItpdConf(itpdConf) + + restartItpdIfNeeded(context) + } + } + + private fun getItpdConf(): List = try { + File(pathVars.itpdConfPath).readLines() + } catch (e: Exception) { + loge("ItpdSubscriptionsViewModel getItpdConf", e) + emptyList() + } + + private fun saveItpdConf(lines: List) = try { + File(pathVars.itpdConfPath).printWriter().use { + lines.forEach { line -> it.println(line) } + } + } catch (e: Exception) { + loge("ItpdSubscriptionsViewModel saveItpdConf", e) + } + + private fun restartItpdIfNeeded(context: Context) { + val itpdRunning = ModulesAux.isITPDSavedStateRunning() + if (itpdRunning) { + ModulesRestarter.restartITPD(context) + } + } +} diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/PreferencesITPDFragment.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/itpd_settings/PreferencesITPDFragment.java similarity index 98% rename from tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/PreferencesITPDFragment.java rename to tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/itpd_settings/PreferencesITPDFragment.java index aec149281..85ebe6fa0 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/PreferencesITPDFragment.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/itpd_settings/PreferencesITPDFragment.java @@ -17,7 +17,7 @@ Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com */ -package pan.alexander.tordnscrypt.settings; +package pan.alexander.tordnscrypt.settings.itpd_settings; import android.app.Activity; import android.content.Context; @@ -42,6 +42,9 @@ import pan.alexander.tordnscrypt.R; import pan.alexander.tordnscrypt.modules.ModulesAux; import pan.alexander.tordnscrypt.modules.ModulesStatus; +import pan.alexander.tordnscrypt.settings.ConfigEditorFragment; +import pan.alexander.tordnscrypt.settings.PathVars; +import pan.alexander.tordnscrypt.settings.SettingsActivity; import pan.alexander.tordnscrypt.utils.executors.CoroutineExecutor; import pan.alexander.tordnscrypt.utils.filemanager.FileManager; import pan.alexander.tordnscrypt.modules.ModulesRestarter; diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/show_rules/ShowRulesRecycleFrag.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/show_rules/ShowRulesRecycleFrag.java deleted file mode 100644 index 03900a83a..000000000 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/show_rules/ShowRulesRecycleFrag.java +++ /dev/null @@ -1,413 +0,0 @@ -/* - This file is part of InviZible Pro. - - InviZible Pro is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - InviZible Pro is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with InviZible Pro. If not, see . - - Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com - */ - -package pan.alexander.tordnscrypt.settings.show_rules; - -import android.app.Activity; -import android.content.Context; -import android.content.SharedPreferences; -import android.os.Bundle; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.google.android.material.floatingactionbutton.FloatingActionButton; - -import androidx.appcompat.widget.SwitchCompat; -import androidx.fragment.app.DialogFragment; -import androidx.fragment.app.Fragment; -import androidx.preference.PreferenceManager; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import android.text.Editable; -import android.text.TextWatcher; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CompoundButton; -import android.widget.EditText; -import android.widget.ImageButton; -import android.widget.TextView; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; - -import pan.alexander.tordnscrypt.R; -import pan.alexander.tordnscrypt.settings.SettingsActivity; -import pan.alexander.tordnscrypt.dialogs.NotificationDialogFragment; -import pan.alexander.tordnscrypt.modules.ModulesAux; -import pan.alexander.tordnscrypt.utils.filemanager.FileManager; -import pan.alexander.tordnscrypt.modules.ModulesRestarter; - -import static pan.alexander.tordnscrypt.utils.logger.Logger.loge; - - -public class ShowRulesRecycleFrag extends Fragment implements View.OnClickListener { - - private RecyclerView mRecyclerView; - private RecyclerView.Adapter mAdapter; - - private final ArrayList rules_file = new ArrayList<>(); - private final ArrayList rules_list = new ArrayList<>(); - private final ArrayList others_list = new ArrayList<>(); - private final ArrayList original_rules = new ArrayList<>(); - - private String file_path; - private boolean readOnly; - - - public ShowRulesRecycleFrag() { - } - - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setRetainInstance(true); - - if (getArguments() != null) { - List rules = getArguments().getStringArrayList("rules_file"); - rules_file.addAll(rules != null ? rules : Collections.emptyList()); - file_path = getArguments().getString("path"); - } - - } - - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - - View view; - try { - view = inflater.inflate(R.layout.fragment_show_rules_recycle, container, false); - } catch (Exception e) { - loge("ShowRulesRecycleFrag onCreateView", e); - throw e; - } - - RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(container.getContext()); - mRecyclerView = view.findViewById(R.id.rvRules); - mRecyclerView.setLayoutManager(mLayoutManager); - - if (checkAndTrimIfRulesTooMany()) { - readOnly = true; - showTooManyRulesDialog(); - } - - FloatingActionButton btnAddRule = view.findViewById(R.id.floatingBtnAddRule); - if (readOnly) { - btnAddRule.setVisibility(View.GONE); - } else { - btnAddRule.setAlpha(0.8f); - btnAddRule.setOnClickListener(this); - btnAddRule.requestFocus(); - } - - return view; - } - - @Override - public void onResume() { - super.onResume(); - - Activity activity = getActivity(); - if (activity == null) { - return; - } - - setTitle(activity); - - if (rules_list.isEmpty()) { - fillRules(); - } - - mAdapter = new RulesAdapter(rules_list); - mRecyclerView.setAdapter(mAdapter); - } - - @Override - public void onStop() { - super.onStop(); - - Context context = getActivity(); - if (context == null) { - return; - } - - List rules_file_new = new LinkedList<>(); - - for (Rules rule : rules_list) { - if (rule.active) { - rules_file_new.add(rule.text); - } else { - rules_file_new.add("#" + rule.text); - } - rules_file_new.add(""); - } - - if (rules_file_new.equals(original_rules)) return; - - if (file_path.contains("subscriptions")) { - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); - StringBuilder sb = new StringBuilder(); - String str = ""; - for (Rules rule : rules_list) { - if (rule.subscription) - sb.append(rule.text).append(", "); - } - if (sb.length() > 2) { - str = sb.substring(0, sb.length() - 2); - } - sp.edit().putString("subscriptions", str).apply(); - - rules_file.clear(); - rules_file.addAll(rules_file_new); - original_rules.clear(); - original_rules.addAll(rules_file_new); - } else { - rules_file.clear(); - rules_file.addAll(others_list); - rules_file.addAll(rules_file_new); - original_rules.clear(); - original_rules.addAll(rules_file_new); - - FileManager.writeToTextFile(context, file_path, rules_file, SettingsActivity.rules_tag); - } - - boolean dnsCryptRunning = ModulesAux.isDnsCryptSavedStateRunning(); - boolean itpdRunning = ModulesAux.isITPDSavedStateRunning(); - - if (itpdRunning && file_path.contains("subscriptions")) { - ModulesRestarter.restartITPD(context); - } else if (dnsCryptRunning) { - ModulesRestarter.restartDNSCrypt(context); - } - } - - private boolean checkAndTrimIfRulesTooMany() { - if (rules_file.size() > 1000) { - rules_file.subList(1000, rules_file.size()).clear(); - rules_file.trimToSize(); - - return true; - } - - return false; - } - - private void showTooManyRulesDialog() { - DialogFragment dialogFragment = NotificationDialogFragment.newInstance(R.string.dnscrypt_many_rules_dialog_message); - if (isAdded()) { - dialogFragment.show(getChildFragmentManager(), "TooManyRules"); - } - } - - private void setTitle(Activity activity) { - if (file_path.endsWith("forwarding-rules.txt")) { - activity.setTitle(R.string.title_dnscrypt_forwarding_rules); - } else if (file_path.endsWith("cloaking-rules.txt")) { - activity.setTitle(R.string.title_dnscrypt_cloaking_rules); - } else if (file_path.endsWith("ip-blacklist.txt")) { - activity.setTitle(R.string.title_dnscrypt_ip_blacklist); - } else if (file_path.endsWith("blacklist.txt")) { - activity.setTitle(R.string.title_dnscrypt_blacklist); - } else if (file_path.endsWith("whitelist.txt")) { - activity.setTitle(R.string.title_dnscrypt_whitelist); - } else if (file_path.endsWith("subscriptions")) { - activity.setTitle(R.string.pref_itpd_addressbook_subscriptions); - } - } - - private void fillRules() { - String[] lockedItems = {".i2p", "onion"}; - - for (int i = 0; i < rules_file.size(); i++) { - boolean match = !rules_file.get(i).matches("#.*#.*") && !rules_file.get(i).isEmpty(); - boolean active = !rules_file.get(i).contains("#"); - boolean locked = false; - boolean subscription = file_path.contains("subscriptions") && !rules_file.get(i).isEmpty(); - - for (String str : lockedItems) { - if (rules_file.get(i).matches(".?" + str + ".*")) { - locked = true; - break; - } - } - if (match) { - rules_list.add(new Rules(rules_file.get(i).replace("#", ""), active, locked, subscription)); - original_rules.add(rules_file.get(i)); - original_rules.add(""); - } else if (!rules_file.get(i).isEmpty()) { - others_list.add(rules_file.get(i)); - others_list.add(""); - } - } - } - - @Override - public void onClick(View v) { - boolean subscription = file_path.contains("subscriptions"); - rules_list.add(new Rules("", true, false, subscription)); - mAdapter.notifyItemInserted(rules_list.size() - 1); - mRecyclerView.scrollToPosition(rules_list.size() - 1); - } - - class RulesAdapter extends RecyclerView.Adapter { - - ArrayList list_rules_adapter; - LayoutInflater lInflater = (LayoutInflater) requireActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); - - RulesAdapter(ArrayList rules_list) { - list_rules_adapter = rules_list; - } - - - @NonNull - @Override - public RuleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View view; - try { - view = lInflater.inflate(R.layout.item_rules, parent, false); - } catch (Exception e) { - loge("ShowRulesRecycleFrag onCreateViewHolder", e); - throw e; - } - return new RuleViewHolder(view); - } - - @Override - public void onBindViewHolder(@NonNull RulesAdapter.RuleViewHolder holder, int position) { - holder.bind(position); - } - - @Override - public int getItemCount() { - return list_rules_adapter.size(); - } - - Rules getRule(int position) { - return list_rules_adapter.get(position); - } - - void delRule(int position) { - - try { - list_rules_adapter.remove(position); - } catch (Exception e) { - loge("ShowRulesRecycleFrag getItemCount", e); - } - - - } - - class RuleViewHolder extends RecyclerView.ViewHolder - implements View.OnClickListener, CompoundButton.OnCheckedChangeListener, View.OnFocusChangeListener { - - EditText etRule; - ImageButton delBtnRules; - SwitchCompat swRuleActive; - - RuleViewHolder(View itemView) { - super(itemView); - - etRule = itemView.findViewById(R.id.etRule); - delBtnRules = itemView.findViewById(R.id.delBtnRules); - swRuleActive = itemView.findViewById(R.id.swRuleActive); - - if (readOnly) { - delBtnRules.setVisibility(View.GONE); - swRuleActive.setVisibility(View.GONE); - } else { - if (!file_path.contains("subscriptions")) { - swRuleActive.setOnCheckedChangeListener(this); - swRuleActive.setOnFocusChangeListener(this); - } - etRule.addTextChangedListener(textWatcher); - delBtnRules.setOnClickListener(this); - } - } - - void bind(int position) { - etRule.setText(getRule(position).text, TextView.BufferType.EDITABLE); - etRule.setEnabled(getRule(position).active); - - if (getRule(position).subscription) { - swRuleActive.setVisibility(View.GONE); - } else if (!readOnly) { - swRuleActive.setVisibility(View.VISIBLE); - swRuleActive.setChecked(getRule(position).active); - } - - delBtnRules.setEnabled(true); - - if (getRule(position).locked) { - delBtnRules.setEnabled(false); - } - } - - TextWatcher textWatcher = new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - int position = getBindingAdapterPosition(); - if (!getRule(position).locked) - getRule(position).text = s.toString(); - } - - @Override - public void afterTextChanged(Editable s) { - } - }; - - @Override - public void onClick(View v) { - if (v.getId() == R.id.delBtnRules) { - int position = getBindingAdapterPosition(); - delRule(position); - notifyItemRemoved(position); - } - } - - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - int position = getBindingAdapterPosition(); - if (getRule(position).active != isChecked) { - getRule(position).active = isChecked; - notifyItemChanged(position); - } - } - - @Override - public void onFocusChange(View v, boolean hasFocus) { - if (hasFocus) { - mRecyclerView.smoothScrollToPosition(getBindingAdapterPosition()); - } - } - } - } - -} diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/tor_bridges/PreferencesTorBridges.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/tor_bridges/PreferencesTorBridges.java index 95bb41b65..0226a7790 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/tor_bridges/PreferencesTorBridges.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/tor_bridges/PreferencesTorBridges.java @@ -93,6 +93,7 @@ import static pan.alexander.tordnscrypt.di.SharedPreferencesModule.DEFAULT_PREFERENCES_NAME; import static pan.alexander.tordnscrypt.utils.Constants.IPv6_REGEX_NO_BOUNDS; +import static pan.alexander.tordnscrypt.utils.Utils.unescapeHTML; import static pan.alexander.tordnscrypt.utils.enums.BridgeType.conjure; import static pan.alexander.tordnscrypt.utils.enums.BridgeType.webtunnel; import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPED; @@ -384,7 +385,7 @@ public void onStop() { if (fascistFirewallShouldBeDisabled && line.startsWith("ReachableAddresses")) { line = "#" + line; } - if ((line.contains("#") + if ((line.startsWith("#") || (!line.contains("Bridge ") && !line.contains("ClientTransportPlugin ") && !line.contains("UseBridges "))) @@ -744,7 +745,7 @@ private void addBridges(final List persistList) { builder.setPositiveButton(getText(R.string.ok), (dialogInterface, i) -> { List bridgesListNew = new ArrayList<>(); - String inputLinesStr = input.getText().toString().trim(); + String inputLinesStr = unescapeHTML(input.getText().toString().trim()); String bridgeBase; if (isBridgeIPv6(inputLinesStr)) { @@ -843,7 +844,7 @@ private boolean isBridgeIPv6(String bridge) { private void addRequestedBridges(String bridgesToAdd, List savedCustomBridges) { List bridgesListNew = new ArrayList<>(); - String[] bridgesArrNew = bridgesToAdd.split("\n"); + String[] bridgesArrNew = unescapeHTML(bridgesToAdd).split("\n"); if (bridgesArrNew.length != 0) { for (String brgNew : bridgesArrNew) { diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/tor_bridges/PreferencesTorBridgesViewModel.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/tor_bridges/PreferencesTorBridgesViewModel.kt index cd3c85ff9..05dacd0eb 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/tor_bridges/PreferencesTorBridgesViewModel.kt +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/tor_bridges/PreferencesTorBridgesViewModel.kt @@ -252,6 +252,8 @@ class PreferencesTorBridgesViewModel @Inject constructor( logw("PreferencesTorBridgesViewModel requestTorBridgesCaptchaChallenge", e) } catch (e: java.util.concurrent.CancellationException) { logw("PreferencesTorBridgesViewModel requestTorBridgesCaptchaChallenge", e) + } catch (e: IllegalStateException) { + requestTorBridges(transport, ipv6Bridges, "", "") } catch (e: Exception) { e.message?.let { showErrorMessage(it) } loge("PreferencesTorBridgesViewModel requestTorBridgesCaptchaChallenge", e) diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/tor_bridges/SnowflakeConfigurator.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/tor_bridges/SnowflakeConfigurator.java index 9325a994f..672131cb1 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/tor_bridges/SnowflakeConfigurator.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/tor_bridges/SnowflakeConfigurator.java @@ -44,10 +44,9 @@ public class SnowflakeConfigurator { private static final int SOCKS_ARGUMENT_MAX_LENGTH = 510; private static final int AMP_CACHE = 1; - private static final int FASTLY = 2; - private static final int CDN77 = 3; - private static final int AZURE = 4; - private static final int AMAZON = 5; + private static final int CDN77 = 2; + private static final int AZURE = 3; + private static final int AMAZON = 4; private final Context context; private final Lazy defaultPreferences; @@ -118,8 +117,6 @@ private String getURL(int rendezvousType) { if (rendezvous == AMP_CACHE) { return "https://snowflake-broker.torproject.net/" + " ampcache=https://cdn.ampproject.org/"; - } else if (rendezvous == FASTLY) { - return "https://snowflake-broker.torproject.net.global.prod.fastly.net/"; } else if (rendezvous == CDN77) { return "https://1098762253.rsc.cdn77.org/"; } else if (rendezvous == AZURE) { @@ -133,8 +130,6 @@ private String getFront(int rendezvousType) { int rendezvous = getRendezvous(rendezvousType); if (rendezvous == AMP_CACHE) { return "www.google.com,cdn.ampproject.org"; - } else if (rendezvous == FASTLY) { - return "github.githubassets.com,www.shazam.com,www.cosmopolitan.com,www.esquire.com"; } else if (rendezvous == CDN77) { return "docs.plesk.com,www.phpmyadmin.net,app.datapacket.com"; } else if (rendezvous == AZURE) { diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/tor_fragment/TorRunFragment.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/tor_fragment/TorRunFragment.java index 59e98687b..672d8d789 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/tor_fragment/TorRunFragment.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/tor_fragment/TorRunFragment.java @@ -43,13 +43,19 @@ import android.widget.ScrollView; import android.widget.TextView; +import pan.alexander.tordnscrypt.App; import pan.alexander.tordnscrypt.MainActivity; import pan.alexander.tordnscrypt.R; import pan.alexander.tordnscrypt.TopFragment; +import pan.alexander.tordnscrypt.modules.ModulesStatus; import pan.alexander.tordnscrypt.utils.root.RootExecService; import static android.util.TypedValue.COMPLEX_UNIT_PX; import static pan.alexander.tordnscrypt.TopFragment.TorVersion; +import static pan.alexander.tordnscrypt.utils.enums.ModuleState.FAULT; +import static pan.alexander.tordnscrypt.utils.enums.ModuleState.RUNNING; +import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPED; +import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPING; import static pan.alexander.tordnscrypt.utils.logger.Logger.loge; import com.google.android.material.divider.MaterialDivider; @@ -199,10 +205,28 @@ public void onDestroyView() { @Override public void onClick(View v) { if (v.getId() == R.id.btnTorStart) { + letFirewallStop(); presenter.startButtonOnClick(); } } + private void letFirewallStop() { + ModulesStatus modulesStatus = ModulesStatus.getInstance(); + if (modulesStatus.getFirewallState() != STOPPED + && modulesStatus.getTorState() == RUNNING + && (modulesStatus.getDnsCryptState() == STOPPED + || modulesStatus.getDnsCryptState() == STOPPING + || modulesStatus.getDnsCryptState() == FAULT) + && (modulesStatus.getItpdState() == STOPPED + || modulesStatus.getItpdState() == STOPPING + || modulesStatus.getItpdState() == FAULT)) { + modulesStatus.setFirewallState( + STOPPING, + App.getInstance().getDaggerComponent().getPreferenceRepository().get() + ); + } + } + @Override public void setTorStatus(int resourceText, int resourceColor) { tvTorStatus.setText(resourceText); diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/Constants.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/Constants.java index 88eb86670..304a9ce3e 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/Constants.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/Constants.java @@ -90,7 +90,6 @@ public interface Constants { String DNSCRYPT_RESOLVERS_SOURCE_IPV6 = "https://ipv6.download.dnscrypt.info/resolvers-list/v3/public-resolvers.md"; String DNSCRYPT_RELAYS_SOURCE_IPV6 = "https://ipv6.download.dnscrypt.info/resolvers-list/v3/relays.md"; - int SITES_IPS_REFRESH_JOB_ID = 1; int DEFAULT_SITES_IPS_REFRESH_INTERVAL = 12; int NFLOG_GROUP = 78; @@ -110,4 +109,5 @@ public interface Constants { String NUMBER_REGEX = "\\d+"; String HOST_NAME_REGEX = "[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_\\+.~#?&//=]*)"; + String URL_REGEX = "(https?:\\/\\/)?(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_\\+.~#?&//=]*)"; } diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/Utils.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/Utils.kt index 0e3ace2b1..c26104b54 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/Utils.kt +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/Utils.kt @@ -32,6 +32,7 @@ import android.os.Build import android.os.Environment import android.os.Handler import android.os.Process +import android.text.Html import android.util.Base64 import android.util.TypedValue import android.view.Display @@ -55,7 +56,10 @@ import pan.alexander.tordnscrypt.utils.filemanager.FileShortener import pan.alexander.tordnscrypt.utils.logger.Logger.loge import pan.alexander.tordnscrypt.utils.logger.Logger.logi import pan.alexander.tordnscrypt.utils.logger.Logger.logw -import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.* +import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.ALWAYS_SHOW_HELP_MESSAGES +import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.CHILD_LOCK_PASSWORD +import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.DEFAULT_BRIDGES_OBFS +import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.OWN_BRIDGES_OBFS import pan.alexander.tordnscrypt.utils.root.RootCommands import pan.alexander.tordnscrypt.utils.root.RootCommandsMark.NULL_MARK import java.io.File @@ -63,6 +67,8 @@ import java.io.PrintWriter import java.net.Inet4Address import java.net.NetworkInterface import java.net.SocketException +import java.util.Locale +import java.util.regex.Pattern import kotlin.math.roundToInt @@ -389,4 +395,37 @@ object Utils { imm.hideSoftInputFromWindow(view.windowToken, 0) } + @JvmStatic + fun unescapeHTML(line: String): String { + var result = line + val pattern = Pattern.compile("&#\\d+;") + val matcher = pattern.matcher(line) + if (matcher.find()) { + result = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + matcher.replaceAll( + Html.fromHtml(matcher.group(), Html.FROM_HTML_MODE_LEGACY).toString() + ) + } else { + matcher.replaceAll(Html.fromHtml(matcher.group()).toString()) + } + } + return result + } + + fun formatFileSizeToReadableUnits(length: Long): String { + var bytes = length + if (bytes <= 1024) return String.format(Locale.ROOT, "%d B", bytes) + var u = 0 + while (bytes > 1024 * 1024) { + u++ + bytes = bytes shr 10 + } + return String.format(Locale.ROOT, "%.1f %cB", bytes / 1024f, "kMGTPE"[u]) + } + + fun getDomainNameFromUrl(url: String): String = + url.removePrefix("http://") + .removePrefix("https://") + .substringBefore("/") + } diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/bootcomplete/BootCompleteManager.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/bootcomplete/BootCompleteManager.java index b15a4ad9e..25750a040 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/bootcomplete/BootCompleteManager.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/bootcomplete/BootCompleteManager.java @@ -23,6 +23,7 @@ import static pan.alexander.tordnscrypt.di.SharedPreferencesModule.DEFAULT_PREFERENCES_NAME; import static pan.alexander.tordnscrypt.modules.ModulesServiceActions.ACTION_STOP_SERVICE_FOREGROUND; import static pan.alexander.tordnscrypt.utils.enums.ModuleState.RUNNING; +import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STARTING; import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPED; import static pan.alexander.tordnscrypt.utils.enums.OperationMode.ROOT_MODE; import static pan.alexander.tordnscrypt.utils.enums.OperationMode.UNDEFINED; @@ -53,11 +54,12 @@ import javax.inject.Named; import dagger.Lazy; +import pan.alexander.tordnscrypt.App; import pan.alexander.tordnscrypt.domain.preferences.PreferenceRepository; +import pan.alexander.tordnscrypt.modules.ModulesActionSender; import pan.alexander.tordnscrypt.modules.ModulesAux; import pan.alexander.tordnscrypt.modules.ModulesKiller; import pan.alexander.tordnscrypt.modules.ModulesRunner; -import pan.alexander.tordnscrypt.modules.ModulesService; import pan.alexander.tordnscrypt.modules.ModulesStatus; import pan.alexander.tordnscrypt.modules.ModulesStatusBroadcaster; import pan.alexander.tordnscrypt.settings.PathVars; @@ -108,7 +110,7 @@ public BootCompleteManager( this.updateIPsManager = updateIPsManager; } - private ModulesStatus modulesStatus = ModulesStatus.getInstance(); + private final ModulesStatus modulesStatus = ModulesStatus.getInstance(); public void performAction(final Context context, Intent intent) { @@ -156,11 +158,16 @@ public void performAction(final Context context, Intent intent) { boolean savedDNSCryptStateRunning = ModulesAux.isDnsCryptSavedStateRunning(); boolean savedTorStateRunning = ModulesAux.isTorSavedStateRunning(); boolean savedITPDStateRunning = ModulesAux.isITPDSavedStateRunning(); + boolean savedFirewallStateRunning = ModulesAux.isFirewallSavedStateRunning(); + + boolean autoStartFirewall = autoStartDNSCrypt || autoStartTor + || autoStartITPD && savedFirewallStateRunning; if (action.equalsIgnoreCase(MY_PACKAGE_REPLACED) || action.equalsIgnoreCase(ALWAYS_ON_VPN)) { autoStartDNSCrypt = savedDNSCryptStateRunning; autoStartTor = savedTorStateRunning; autoStartITPD = savedITPDStateRunning; + autoStartFirewall = autoStartDNSCrypt || autoStartTor || savedFirewallStateRunning; } else if (action.equals(SHELL_SCRIPT_CONTROL)) { int startDnsCrypt = intent.getIntExtra(MANAGE_DNSCRYPT_EXTRA, -1); int startTor = intent.getIntExtra(MANAGE_TOR_EXTRA, -1); @@ -185,13 +192,17 @@ public void performAction(final Context context, Intent intent) { broadcastItpdState(autoStartITPD); } + autoStartFirewall = autoStartDNSCrypt || autoStartTor + || autoStartITPD && savedFirewallStateRunning; + logi("SHELL_SCRIPT_CONTROL start: " + "DNSCrypt " + autoStartDNSCrypt + " Tor " + autoStartTor + " ITPD " + autoStartITPD); } else { resetModulesSavedState(preferences); } - if (savedDNSCryptStateRunning || savedTorStateRunning || savedITPDStateRunning) { + if (savedDNSCryptStateRunning || savedTorStateRunning || savedITPDStateRunning + || savedFirewallStateRunning) { stopServicesForeground(context, mode, fixTTL); } @@ -219,28 +230,29 @@ public void performAction(final Context context, Intent intent) { } if (autoStartDNSCrypt && autoStartTor && autoStartITPD) { - startStopRestartModules(true, true, true); + startStopRestartModules(true, true, true, autoStartFirewall); } else if (autoStartDNSCrypt && autoStartTor) { - startStopRestartModules(true, true, false); + startStopRestartModules(true, true, false, autoStartFirewall); } else if (autoStartDNSCrypt && !autoStartITPD) { - startStopRestartModules(true, false, false); + startStopRestartModules(true, false, false, autoStartFirewall); updateIPsManager.get().stopRefreshTorUnlockIPs(); } else if (!autoStartDNSCrypt && autoStartTor && !autoStartITPD) { - startStopRestartModules(false, true, false); + startStopRestartModules(false, true, false, autoStartFirewall); } else if (!autoStartDNSCrypt && !autoStartTor && autoStartITPD) { - startStopRestartModules(false, false, true); + startStopRestartModules(false, false, true, autoStartFirewall); updateIPsManager.get().stopRefreshTorUnlockIPs(); } else if (!autoStartDNSCrypt && autoStartTor) { - startStopRestartModules(false, true, true); + startStopRestartModules(false, true, true, autoStartFirewall); } else if (autoStartDNSCrypt) { - startStopRestartModules(true, false, true); + startStopRestartModules(true, false, true, autoStartFirewall); updateIPsManager.get().stopRefreshTorUnlockIPs(); } else { - startStopRestartModules(false, false, false); + startStopRestartModules(false, false, false, autoStartFirewall); updateIPsManager.get().stopRefreshTorUnlockIPs(); } - if ((autoStartDNSCrypt || autoStartTor || autoStartITPD) && (mode == VPN_MODE || fixTTL)) { + if ((autoStartDNSCrypt || autoStartTor || autoStartITPD || autoStartFirewall) + && (mode == VPN_MODE || fixTTL)) { final Intent prepareIntent = VpnService.prepare(context); if (prepareIntent == null) { @@ -323,7 +335,12 @@ private void startHOTSPOT(PreferenceRepository preferences) { } } - private void startStopRestartModules(boolean autoStartDNSCrypt, boolean autoStartTor, boolean autoStartITPD) { + private void startStopRestartModules( + boolean autoStartDNSCrypt, + boolean autoStartTor, + boolean autoStartITPD, + boolean autoStartFirewall + ) { ModulesStatus modulesStatus = ModulesStatus.getInstance(); @@ -354,6 +371,15 @@ private void startStopRestartModules(boolean autoStartDNSCrypt, boolean autoStar modulesStatus.setItpdState(STOPPED); } + if (autoStartFirewall) { + modulesStatus.setFirewallState(STARTING, preferenceRepository.get()); + if (!autoStartDNSCrypt && !autoStartTor) { + ModulesAux.makeModulesStateExtraLoop(context); + } + } else { + modulesStatus.setFirewallState(STOPPED, preferenceRepository.get()); + } + saveModulesStateRunning(autoStartDNSCrypt, autoStartTor, autoStartITPD); } @@ -368,6 +394,7 @@ private void resetModulesSavedState(PreferenceRepository preferences) { preferences.setStringPreference(SAVED_DNSCRYPT_STATE_PREF, ModuleState.UNDEFINED.toString()); preferences.setStringPreference(SAVED_TOR_STATE_PREF, ModuleState.UNDEFINED.toString()); preferences.setStringPreference(SAVED_ITPD_STATE_PREF, ModuleState.UNDEFINED.toString()); + ModulesAux.saveFirewallStateRunning(false); } private void runDNSCrypt() { @@ -401,13 +428,19 @@ private void stopServicesForeground(Context context, OperationMode mode, boolean Intent stopVPNServiceForeground = new Intent(context, VpnService.class); stopVPNServiceForeground.setAction(ACTION_STOP_SERVICE_FOREGROUND); stopVPNServiceForeground.putExtra("showNotification", true); - context.startForegroundService(stopVPNServiceForeground); + if (App.getInstance().isAppForeground()) { + try { + context.startService(stopVPNServiceForeground); + } catch (Exception e) { + loge("BootCompleteReceiver stopServicesForeground", e); + context.startForegroundService(stopVPNServiceForeground); + } + } else { + context.startForegroundService(stopVPNServiceForeground); + } } - Intent stopModulesServiceForeground = new Intent(context, ModulesService.class); - stopModulesServiceForeground.setAction(ACTION_STOP_SERVICE_FOREGROUND); - context.startForegroundService(stopModulesServiceForeground); - stopModulesServiceForeground.putExtra("showNotification", true); + ModulesActionSender.INSTANCE.sendIntent(context, ACTION_STOP_SERVICE_FOREGROUND); logi("BootCompleteReceiver stop running services foreground"); } diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/connectionchecker/NetworkChecker.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/connectionchecker/NetworkChecker.kt index 831e9dc15..07729ff92 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/connectionchecker/NetworkChecker.kt +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/connectionchecker/NetworkChecker.kt @@ -29,10 +29,8 @@ import android.telephony.TelephonyManager import androidx.annotation.RequiresApi import androidx.core.net.ConnectivityManagerCompat import pan.alexander.tordnscrypt.modules.ModulesStatus -import pan.alexander.tordnscrypt.utils.connectionchecker.NetworkChecker.getConnectivityManager import pan.alexander.tordnscrypt.utils.enums.OperationMode import pan.alexander.tordnscrypt.utils.logger.Logger.loge -import java.util.SortedMap import java.util.TreeMap private const val DEFAULT_MTU = 1400 @@ -78,6 +76,7 @@ object NetworkChecker { private fun hasActiveTransport(capabilities: NetworkCapabilities): Boolean = capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) && (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) + && isCellularInternetMayBeAvailable(capabilities) || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) @@ -123,6 +122,7 @@ object NetworkChecker { private fun hasCellularTransport(capabilities: NetworkCapabilities): Boolean = capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) + && isCellularInternetMayBeAvailable(capabilities) @JvmStatic @@ -171,6 +171,7 @@ object NetworkChecker { private fun hasRoamingTransport(capabilities: NetworkCapabilities): Boolean = capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) + && isCellularInternetMayBeAvailable(capabilities) && !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING) @JvmStatic @@ -310,6 +311,29 @@ object NetworkChecker { false } + private fun getVpnInterfaceName(context: Context): String = try { + val connectivityManager = context.getConnectivityManager() + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && connectivityManager != null) { + connectivityManager.allNetworks.let { + for (network in it) { + val networkCapabilities = + connectivityManager.getNetworkCapabilities(network) + if (networkCapabilities != null && hasVpnTransport(networkCapabilities)) { + return connectivityManager.getLinkProperties(network)?.interfaceName ?: "" + } + } + return "" + } + + } else { + "" + } + } catch (e: Exception) { + loge("NetworkChecker getVpnInterfaceName", e) + "" + } + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) private fun hasVpnTransport(capabilities: NetworkCapabilities): Boolean = capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN) @@ -376,7 +400,8 @@ object NetworkChecker { capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> networks[2] = network - capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> + capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) + && isCellularInternetMayBeAvailable(capabilities) -> networks[3] = network } } else if (capabilities != null @@ -389,7 +414,8 @@ object NetworkChecker { capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> networks[5] = network - capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> + capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) + && isCellularInternetMayBeAvailable(capabilities) -> networks[6] = network } } @@ -409,6 +435,47 @@ object NetworkChecker { return networks.values.toTypedArray() } + //This is required because the cellular interface may only be available for ims + private fun isCellularInternetMayBeAvailable(capabilities: NetworkCapabilities): Boolean { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + } + return true + } + + @JvmStatic + @RequiresApi(Build.VERSION_CODES.M) + fun getCurrentActiveInterface(context: Context): String = try { + val connectivityManager = context.getConnectivityManager() + if (connectivityManager != null) { + getVpnInterfaceName(context).ifEmpty { + connectivityManager.activeNetwork?.let { + connectivityManager.getLinkProperties(it)?.interfaceName + } ?: "" + } + } else { + "" + } + } catch (e: Exception) { + loge("NetworkChecker getCurrentActiveInterface", e) + "" + } + + @JvmStatic + @RequiresApi(Build.VERSION_CODES.M) + fun getUnderlyingVpnActiveInterface(context: Context): String = try { + val connectivityManager = context.getConnectivityManager() + if (connectivityManager != null) { + connectivityManager.activeNetwork?.let { + connectivityManager.getLinkProperties(it)?.interfaceName + } ?: "" + } else { + "" + } + } catch (e: Exception) { + loge("NetworkChecker getUnderlyingVpnActiveInterface", e) + "" + } private fun Context.getConnectivityManager(): ConnectivityManager? = getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/mode/AppModeManager.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/mode/AppModeManager.kt index 8f1517789..a6c4aa44b 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/mode/AppModeManager.kt +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/mode/AppModeManager.kt @@ -100,6 +100,7 @@ class AppModeManager @Inject constructor( //This stop iptables adaptation modulesStatus.mode = OperationMode.PROXY_MODE + modulesStatus.setFirewallState(ModuleState.STOPPED, preferenceRepository.get()) if (modulesStatus.isRootAvailable && operationMode == OperationMode.ROOT_MODE) { val iptablesRules: IptablesRules = ModulesIptablesRules(context) val commands = iptablesRules.clearAll() @@ -137,9 +138,11 @@ class AppModeManager @Inject constructor( val dnsCryptState: ModuleState = modulesStatus.dnsCryptState val torState: ModuleState = modulesStatus.torState val itpdState: ModuleState = modulesStatus.itpdState + val firewallState: ModuleState = modulesStatus.firewallState if (dnsCryptState != ModuleState.STOPPED || torState != ModuleState.STOPPED || itpdState != ModuleState.STOPPED + || firewallState != ModuleState.STOPPED ) { if (modulesStatus.isUseModulesWithRoot) { Toast.makeText(context, "Stop modules...", Toast.LENGTH_LONG).show() diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/preferences/PreferenceKeys.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/preferences/PreferenceKeys.java index 6e2a27a25..5ae1a34bc 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/preferences/PreferenceKeys.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/preferences/PreferenceKeys.java @@ -87,6 +87,12 @@ public interface PreferenceKeys { String GP_DATA = "gpData"; String GP_SIGNATURE = "gpSign"; + String REMOTE_BLACKLIST_URL = "remote_blacklist_url"; + String REMOTE_WHITELIST_URL = "remote_whitelist_url"; + String REMOTE_IP_BLACKLIST_URL = "remote_ip_blacklist_url"; + String REMOTE_FORWARDING_URL = "remote_forwarding_url"; + String REMOTE_CLOAKING_URL = "remote_cloaking_url"; + //VPN String VPN_SERVICE_ENABLED = "VPNServiceEnabled"; @@ -117,6 +123,7 @@ public interface PreferenceKeys { String MULTI_USER_SUPPORT = "pref_common_multi_user"; String REFRESH_RULES = "swRefreshRules"; String KILL_SWITCH = "swKillSwitch"; + String ALWAYS_ON_VPN = "always_on_vpn"; String USE_IPTABLES = "pref_common_use_iptables"; String WAIT_IPTABLES = "pref_common_wait_iptables"; String REMOTE_CONTROL = "pref_common_shell_control"; @@ -140,6 +147,10 @@ public interface PreferenceKeys { String DNSCRYPT_OUTBOUND_PROXY = "Enable proxy"; + String DNSCRYPT_SERVERS_REFRESH_DELAY = "refresh_delay"; + String DNSCRYPT_RELAYS_REFRESH_DELAY = "refresh_delay_relays"; + String DNSCRYPT_RULES_REFRESH_DELAY = "refresh_delay_rules"; + //Tor Settings String TOR_DNS_PORT = "DNSPort"; String TOR_SOCKS_PORT = "SOCKSPort"; diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/privatedns/PrivateDnsProxyManager.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/privatedns/PrivateDnsProxyManager.kt index 7894bc177..005c4b33d 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/privatedns/PrivateDnsProxyManager.kt +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/privatedns/PrivateDnsProxyManager.kt @@ -39,19 +39,27 @@ import pan.alexander.tordnscrypt.R import pan.alexander.tordnscrypt.modules.ModulesStatus import pan.alexander.tordnscrypt.utils.Utils.areNotificationsNotAllowed import pan.alexander.tordnscrypt.utils.connectionchecker.NetworkChecker +import pan.alexander.tordnscrypt.utils.enums.ModuleState import pan.alexander.tordnscrypt.utils.enums.OperationMode import pan.alexander.tordnscrypt.utils.logger.Logger.loge import pan.alexander.tordnscrypt.utils.logger.Logger.logi import pan.alexander.tordnscrypt.vpn.VpnUtils +import pan.alexander.tordnscrypt.vpn.VpnUtils.PRIVATE_DNS_MODE_OPPORTUNISTIC +import pan.alexander.tordnscrypt.vpn.VpnUtils.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME const val DISABLE_PRIVATE_DNS_NOTIFICATION = 167 const val DISABLE_PROXY_NOTIFICATION = 168 object PrivateDnsProxyManager { @RequiresApi(Build.VERSION_CODES.P) - fun checkPrivateDNSAndProxy(context: Context, linkProperties: LinkProperties?) { + fun checkPrivateDNSAndProxy( + context: Context, + linkProperties: LinkProperties?, + ignoreSystemDns: Boolean + ) { try { - if (ModulesStatus.getInstance().mode == OperationMode.PROXY_MODE) { + val modulesStatus = ModulesStatus.getInstance() + if (modulesStatus.mode == OperationMode.PROXY_MODE) { return } @@ -68,7 +76,14 @@ object PrivateDnsProxyManager { // localLinkProperties.privateDnsServerName == null - Opportunistic mode ("Automatic") - if (VpnUtils.isPrivateDns(context) || localLinkProperties?.isPrivateDnsActive == true) { + val privateDnsMode = VpnUtils.getPrivateDnsMode(context) + if ((modulesStatus.dnsCryptState == ModuleState.RUNNING + || modulesStatus.torState == ModuleState.RUNNING) + && (privateDnsMode == PRIVATE_DNS_MODE_PROVIDER_HOSTNAME + || privateDnsMode == PRIVATE_DNS_MODE_OPPORTUNISTIC && !ignoreSystemDns + || localLinkProperties?.isPrivateDnsActive == true + && (localLinkProperties.privateDnsServerName != null || !ignoreSystemDns)) + ) { sendNotification( context, context.getString(R.string.app_name), @@ -77,7 +92,10 @@ object PrivateDnsProxyManager { ) } - if (localLinkProperties?.httpProxy != null) { + if ((modulesStatus.dnsCryptState == ModuleState.RUNNING + || modulesStatus.torState == ModuleState.RUNNING) + && localLinkProperties?.httpProxy != null + ) { if (NetworkChecker.isWifiActive(context)) { sendNotification( diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/VpnUtils.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/VpnUtils.java index e0bcf9210..9ee3841ba 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/VpnUtils.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/VpnUtils.java @@ -19,6 +19,7 @@ package pan.alexander.tordnscrypt.vpn; +import android.content.ContentResolver; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; @@ -28,6 +29,7 @@ import android.net.Network; import android.os.Build; import android.provider.Settings; +import android.text.TextUtils; import androidx.annotation.Keep; @@ -245,13 +247,31 @@ public static boolean canFilter() { } } - public static boolean isPrivateDns(Context context) { - String dns_mode = Settings.Global.getString(context.getContentResolver(), "private_dns_mode"); - logi("Private DNS mode=" + dns_mode); - if (dns_mode == null) { - dns_mode = "off"; + public static final int PRIVATE_DNS_MODE_OFF = 1; + public static final int PRIVATE_DNS_MODE_OPPORTUNISTIC = 2; + public static final int PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = 3; + public static final String PRIVATE_DNS_DEFAULT_MODE = "private_dns_default_mode"; + public static final String PRIVATE_DNS_MODE = "private_dns_mode"; + + public static int getPrivateDnsMode(Context context) { + try { + final ContentResolver cr = context.getContentResolver(); + String mode = Settings.Global.getString(cr, PRIVATE_DNS_MODE); + if (TextUtils.isEmpty(mode)) mode = Settings.Global.getString(cr, PRIVATE_DNS_DEFAULT_MODE); + return getPrivateDnsModeAsInt(mode); + } catch (Exception e) { + loge("VpnUtils getPrivateDnsMode", e); } - return (!"off".equals(dns_mode)); + return PRIVATE_DNS_MODE_OFF; + } + private static int getPrivateDnsModeAsInt(String mode) { + if (TextUtils.isEmpty(mode)) + return PRIVATE_DNS_MODE_OFF; + return switch (mode) { + case "hostname" -> PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; + case "opportunistic" -> PRIVATE_DNS_MODE_OPPORTUNISTIC; + default -> PRIVATE_DNS_MODE_OFF; + }; } public static boolean isIpInSubnetOld(final String ip, final String network) { diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/ServiceVPN.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/ServiceVPN.java index 0d00df68c..d53e12ec6 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/ServiceVPN.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/ServiceVPN.java @@ -64,6 +64,7 @@ import pan.alexander.tordnscrypt.modules.UsageStatistics; import pan.alexander.tordnscrypt.settings.PathVars; import pan.alexander.tordnscrypt.utils.Utils; +import pan.alexander.tordnscrypt.utils.enums.OperationMode; import pan.alexander.tordnscrypt.utils.enums.VPNCommand; import pan.alexander.tordnscrypt.utils.executors.CoroutineExecutor; import pan.alexander.tordnscrypt.vpn.Allowed; @@ -97,6 +98,7 @@ import static pan.alexander.tordnscrypt.utils.bootcomplete.BootCompleteManager.ALWAYS_ON_VPN; import static pan.alexander.tordnscrypt.utils.enums.ModuleState.RESTARTING; import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STARTING; +import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPED; import static pan.alexander.tordnscrypt.utils.logger.Logger.loge; import static pan.alexander.tordnscrypt.utils.logger.Logger.logi; import static pan.alexander.tordnscrypt.utils.logger.Logger.logw; @@ -144,6 +146,7 @@ public class ServiceVPN extends VpnService implements OnInternetConnectionChecke public Lazy vpnRulesHolder; NotificationManager notificationManager; + private ModulesServiceNotificationManager serviceNotificationManager; private static final Object jni_lock = new Object(); private static volatile long jni_context = 0; private volatile long service_jni_context = 0; @@ -613,13 +616,14 @@ public void onCreate() { message = UsageStatistics.getSavedMessage(); } - ModulesServiceNotificationManager notification = new ModulesServiceNotificationManager( + serviceNotificationManager = ModulesServiceNotificationManager.getManager(this); + serviceNotificationManager.createNotificationChannel(this); + serviceNotificationManager.sendNotification( this, - notificationManager, + title, + message, UsageStatistics.getStartTime() ); - notification.createNotificationChannel(); - notification.sendNotification(title, message); } App.getInstance().getSubcomponentsManager().modulesServiceSubcomponent().inject(this); @@ -671,12 +675,16 @@ public int onStartCommand(Intent intent, int flags, int startId) { message = UsageStatistics.getSavedMessage(); } - ModulesServiceNotificationManager notification = new ModulesServiceNotificationManager( - this, - notificationManager, - UsageStatistics.getStartTime() - ); - notification.sendNotification(title, message); + if (serviceNotificationManager == null) { + serviceNotificationManager = ModulesServiceNotificationManager + .getManager(this); + serviceNotificationManager.sendNotification( + this, + title, + message, + UsageStatistics.getStartTime() + ); + } } logi("VPN Received " + intent); @@ -780,6 +788,12 @@ public void onDestroy() { connectionCheckerInteractor.get().removeListener(this); handler.get().removeCallbacksAndMessages(null); + ModulesStatus modulesStatus = ModulesStatus.getInstance(); + if (modulesStatus.getMode() == OperationMode.VPN_MODE + || modulesStatus.getMode() == OperationMode.PROXY_MODE) { + ModulesStatus.getInstance().setFirewallState(STOPPED, preferenceRepository.get()); + } + final long localJniContext = service_jni_context; executor.get().submit("ServiceVPN onDestroy", () -> { diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/ServiceVPNHandler.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/ServiceVPNHandler.java index aaa3e078c..8e508883a 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/ServiceVPNHandler.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/ServiceVPNHandler.java @@ -54,11 +54,15 @@ import pan.alexander.tordnscrypt.settings.PathVars; import pan.alexander.tordnscrypt.utils.connectionchecker.NetworkChecker; import pan.alexander.tordnscrypt.utils.enums.ModuleState; +import pan.alexander.tordnscrypt.utils.enums.OperationMode; import pan.alexander.tordnscrypt.utils.enums.VPNCommand; import pan.alexander.tordnscrypt.vpn.Rule; import static pan.alexander.tordnscrypt.di.SharedPreferencesModule.DEFAULT_PREFERENCES_NAME; import static pan.alexander.tordnscrypt.modules.ModulesService.DEFAULT_NOTIFICATION_ID; +import static pan.alexander.tordnscrypt.utils.enums.ModuleState.RUNNING; +import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STARTING; +import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPING; import static pan.alexander.tordnscrypt.utils.logger.Logger.loge; import static pan.alexander.tordnscrypt.utils.logger.Logger.logi; import static pan.alexander.tordnscrypt.utils.logger.Logger.logw; @@ -205,6 +209,14 @@ private void start() { } serviceVPN.startNative(serviceVPN.vpn, listAllowed); + + ModulesStatus modulesStatus = ModulesStatus.getInstance(); + if (modulesStatus.getMode() == OperationMode.VPN_MODE + && serviceVPN.vpnPreferences.getFirewallEnabled()) { + modulesStatus.setFirewallState(RUNNING, preferenceRepository.get()); + } else { + modulesStatus.setFirewallState(STOPPED, preferenceRepository.get()); + } } } @@ -312,6 +324,16 @@ private void reload() { serviceVPN.reloading = false; + if (modulesStatus.getMode() == OperationMode.VPN_MODE) { + if (modulesStatus.getFirewallState() == STARTING) { + modulesStatus.setFirewallState(RUNNING, preferenceRepository.get()); + } else if (modulesStatus.getFirewallState() == STOPPING) { + modulesStatus.setFirewallState(STOPPED, preferenceRepository.get()); + } + } else { + modulesStatus.setFirewallState(STOPPED, preferenceRepository.get()); + } + if (defaultSharedPreferences.get().getBoolean(ARP_SPOOFING_DETECTION, false)) { try { ArpScanner.getArpComponent().get().reset( @@ -445,6 +467,10 @@ private void stopServiceVPN() { serviceVPN.stopSelf(); ModulesStatus modulesStatus = ModulesStatus.getInstance(); + if (modulesStatus.getMode() == OperationMode.VPN_MODE + || modulesStatus.getMode() == OperationMode.PROXY_MODE) { + modulesStatus.setFirewallState(STOPPED, preferenceRepository.get()); + } ModuleState dnsCryptState = modulesStatus.getDnsCryptState(); ModuleState torState = modulesStatus.getTorState(); ModuleState itpdState = modulesStatus.getItpdState(); diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/ServiceVPNHelper.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/ServiceVPNHelper.java index ff06d5208..66a0a9050 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/ServiceVPNHelper.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/ServiceVPNHelper.java @@ -35,6 +35,7 @@ import pan.alexander.tordnscrypt.utils.enums.OperationMode; import pan.alexander.tordnscrypt.utils.enums.VPNCommand; +import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STARTING; import static pan.alexander.tordnscrypt.utils.logger.Logger.loge; import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.VPN_SERVICE_ENABLED; import static pan.alexander.tordnscrypt.utils.enums.ModuleState.RUNNING; @@ -59,6 +60,7 @@ public static void reload(String reason, Context context) { OperationMode operationMode = modulesStatus.getMode(); ModuleState dnsCryptState = modulesStatus.getDnsCryptState(); ModuleState torState = modulesStatus.getTorState(); + ModuleState firewallState = modulesStatus.getFirewallState(); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); boolean vpnServiceEnabled = prefs.getBoolean(VPN_SERVICE_ENABLED, false); @@ -67,7 +69,8 @@ public static void reload(String reason, Context context) { if (((operationMode == VPN_MODE) || fixTTL) && vpnServiceEnabled - && (dnsCryptState == RUNNING || torState == RUNNING)) { + && (dnsCryptState == RUNNING || torState == RUNNING + || firewallState == RUNNING || firewallState == STARTING)) { Intent intent = new Intent(context, ServiceVPN.class); intent.putExtra(EXTRA_COMMAND, VPNCommand.RELOAD); intent.putExtra(EXTRA_REASON, reason); diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/VpnPreferenceHolder.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/VpnPreferenceHolder.kt index 4342cc1ab..771895a32 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/VpnPreferenceHolder.kt +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/VpnPreferenceHolder.kt @@ -83,7 +83,7 @@ class VpnPreferenceHolder @Inject constructor( && proxyAddress.isNotBlank() && proxyPort != 0 - val torIsolateUid = defaultPreferences.getBoolean(TOR_ISOLATE_UID, false) + val torIsolateUid = defaultPreferences.getBoolean(TOR_ISOLATE_UID, true) val torDNSPort = pathVars.torDNSPort.let { if (it.matches(Regex(NUMBER_REGEX)) && it.toLong() <= MAX_PORT_NUMBER) { diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/VpnReceiver.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/VpnReceiver.java deleted file mode 100644 index 91fa0debf..000000000 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/VpnReceiver.java +++ /dev/null @@ -1,325 +0,0 @@ -/* - This file is part of InviZible Pro. - - InviZible Pro is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - InviZible Pro is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with InviZible Pro. If not, see . - - Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com - */ - -package pan.alexander.tordnscrypt.vpn.service; - -import static pan.alexander.tordnscrypt.di.SharedPreferencesModule.DEFAULT_PREFERENCES_NAME; -import static pan.alexander.tordnscrypt.utils.logger.Logger.loge; -import static pan.alexander.tordnscrypt.utils.logger.Logger.logi; -import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REFRESH_RULES; -import static pan.alexander.tordnscrypt.vpn.service.ServiceVPNHelper.reload; - -import android.annotation.TargetApi; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.net.ConnectivityManager; -import android.net.LinkProperties; -import android.net.Network; -import android.net.NetworkCapabilities; -import android.net.NetworkRequest; -import android.os.Build; -import android.os.PowerManager; -import android.text.TextUtils; -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; - -import java.net.InetAddress; -import java.util.List; - -import javax.inject.Inject; -import javax.inject.Named; - -import dagger.Lazy; -import pan.alexander.tordnscrypt.domain.connection_checker.ConnectionCheckerInteractor; -import pan.alexander.tordnscrypt.settings.firewall.FirewallNotification; -import pan.alexander.tordnscrypt.utils.privatedns.PrivateDnsProxyManager; - -public class VpnReceiver { - - private final Lazy connectionCheckerInteractor; - private final Lazy defaultPreferences; - - private boolean registeredIdleState = false; - private boolean registeredPackageChanged = false; - private boolean registeredConnectivityChanged = false; - private Object networkCallback = null; - private FirewallNotification firewallNotificationReceiver; - - @Inject - public VpnReceiver( - Lazy connectionCheckerInteractor, - @Named(DEFAULT_PREFERENCES_NAME) - Lazy defaultPreferences - ) { - this.connectionCheckerInteractor = connectionCheckerInteractor; - this.defaultPreferences = defaultPreferences; - } - - @RequiresApi(api = Build.VERSION_CODES.M) - void listenIdleStateChanged(ServiceVPN vpn) { - IntentFilter ifIdle = new IntentFilter(); - ifIdle.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); - vpn.registerReceiver(idleStateReceiver, ifIdle); - registeredIdleState = true; - } - - void unlistenIdleStateChanged(ServiceVPN vpn) { - if (registeredIdleState) { - vpn.unregisterReceiver(idleStateReceiver); - registeredIdleState = false; - } - } - - void listenAddRemoveApp(ServiceVPN vpn) { - IntentFilter ifPackage = new IntentFilter(); - ifPackage.addAction(Intent.ACTION_PACKAGE_ADDED); - ifPackage.addAction(Intent.ACTION_PACKAGE_REMOVED); - ifPackage.addDataScheme("package"); - vpn.registerReceiver(packageChangedReceiver, ifPackage); - - registerFirewallReceiver(vpn); - - registeredPackageChanged = true; - } - - void unlistenAddRemoveApp(ServiceVPN vpn) { - if (registeredPackageChanged) { - vpn.unregisterReceiver(packageChangedReceiver); - registeredPackageChanged = false; - - unregisterFirewallReceiver(vpn); - } - } - - private void registerFirewallReceiver(ServiceVPN vpn) { - firewallNotificationReceiver = FirewallNotification.Companion.registerFirewallReceiver(vpn); - } - - private void unregisterFirewallReceiver(ServiceVPN vpn) { - FirewallNotification.Companion.unregisterFirewallReceiver(vpn, firewallNotificationReceiver); - } - - private final BroadcastReceiver idleStateReceiver = new BroadcastReceiver() { - @Override - @TargetApi(Build.VERSION_CODES.M) - public void onReceive(Context context, Intent intent) { - logi("VPN Received " + intent); - - PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); - if (pm != null) { - logi("VPN device idle=" + pm.isDeviceIdleMode()); - } - - // Reload rules when coming from idle mode - if (pm != null && !pm.isDeviceIdleMode()) { - setInternetAvailable(false); - reload("VPN idle state changed", context); - } - } - }; - - private final BroadcastReceiver connectivityChangedReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - // Filter VPN connectivity changes - int networkType = intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, ConnectivityManager.TYPE_DUMMY); - if (networkType == ConnectivityManager.TYPE_VPN) - return; - - // Reload rules - logi("VPN Received " + intent); - setInternetAvailable(false); - reload("Connectivity changed", context); - } - }; - - private final BroadcastReceiver packageChangedReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - logi("VPN Received " + intent); - - try { - if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())) { - reload("VPN Package added", context); - } else if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { - reload("VPN Package deleted", context); - } - } catch (Throwable ex) { - loge(ex.toString() + "\n" + Log.getStackTraceString(ex)); - } - } - }; - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - void listenNetworkChanges(ServiceVPN vpn) { - // Listen for network changes - logi("VPN Starting listening to network changes"); - ConnectivityManager cm = (ConnectivityManager) vpn.getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkRequest.Builder builder = new NetworkRequest.Builder(); - /*builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - builder.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); - }*/ - - ConnectivityManager.NetworkCallback nc = new ConnectivityManager.NetworkCallback() { - private Boolean last_connected = null; - private List last_dns = null; - private int last_network = 0; - - @Override - public void onAvailable(@NonNull Network network) { - logi("VPN Available network=" + network); - connectionCheckerInteractor.get().checkNetworkConnection(); - last_connected = isNetworkAvailable(); - - if (!last_connected) { - last_connected = true; - setInternetAvailable(true); - } - - reload("Network available", vpn); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && last_network != network.hashCode()) { - PrivateDnsProxyManager.INSTANCE.checkPrivateDNSAndProxy( - vpn, null - ); - } - - last_network = network.hashCode(); - } - - @Override - public void onLinkPropertiesChanged(@NonNull Network network, LinkProperties linkProperties) { - // Make sure the right DNS servers are being used - List dns = linkProperties.getDnsServers(); - SharedPreferences prefs = defaultPreferences.get(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O - ? !same(last_dns, dns) - : prefs.getBoolean(REFRESH_RULES, false)) { - logi("VPN Changed link properties=" + linkProperties + - "DNS cur=" + TextUtils.join(",", dns) + - "DNS prv=" + (last_dns == null ? null : TextUtils.join(",", last_dns))); - last_dns = dns; - logi("VPN Changed link properties=" + linkProperties); - - if (network.hashCode() != last_network) { - last_network = network.hashCode(); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - PrivateDnsProxyManager.INSTANCE.checkPrivateDNSAndProxy( - vpn, linkProperties - ); - } - - setInternetAvailable(false); - reload("VPN Link properties changed", vpn); - } - } - } - - @Override - public void onCapabilitiesChanged(@NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) { - connectionCheckerInteractor.get().checkNetworkConnection(); - if (isNetworkAvailable() && (last_connected == null || !last_connected)) { - last_connected = true; - setInternetAvailable(false); - reload("VPN Connected state changed", vpn); - } - - last_network = network.hashCode(); - - logi("VPN Changed capabilities=" + network); - } - - @Override - public void onLost(@NonNull Network network) { - logi("VPN Lost network=" + network); - connectionCheckerInteractor.get().checkNetworkConnection(); - last_connected = isNetworkAvailable(); - - setInternetAvailable(false); - - reload("Network lost", vpn); - - last_network = 0; - } - - boolean same(List last, List current) { - if (last == null || current == null) - return false; - if (last.size() != current.size()) - return false; - - for (int i = 0; i < current.size(); i++) - if (!last.get(i).equals(current.get(i))) - return false; - - return true; - } - }; - if (cm != null) { - cm.registerNetworkCallback(builder.build(), nc); - networkCallback = nc; - } - } - - void unlistenNetworkChanges(ServiceVPN vpn) { - if (networkCallback != null) { - unregisterNetworkChanges(vpn); - networkCallback = null; - } - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - private void unregisterNetworkChanges(ServiceVPN vpn) { - ConnectivityManager cm = (ConnectivityManager) vpn.getSystemService(Context.CONNECTIVITY_SERVICE); - if (cm != null) { - cm.unregisterNetworkCallback((ConnectivityManager.NetworkCallback) networkCallback); - } - } - - void listenConnectivityChanges(ServiceVPN vpn) { - // Listen for connectivity updates - logi("VPN Starting listening to connectivity changes"); - IntentFilter ifConnectivity = new IntentFilter(); - ifConnectivity.addAction(ConnectivityManager.CONNECTIVITY_ACTION); - vpn.registerReceiver(connectivityChangedReceiver, ifConnectivity); - registeredConnectivityChanged = true; - } - - void unlistenConnectivityChanges(ServiceVPN vpn) { - if (registeredConnectivityChanged) { - vpn.unregisterReceiver(connectivityChangedReceiver); - registeredConnectivityChanged = false; - } - } - - private boolean isNetworkAvailable() { - return connectionCheckerInteractor.get().getNetworkConnectionResult(); - } - - private void setInternetAvailable(boolean available) { - connectionCheckerInteractor.get().setInternetConnectionResult(available); - } -} diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/VpnRulesHolder.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/VpnRulesHolder.java index 55b86bb94..c5af713c9 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/VpnRulesHolder.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/VpnRulesHolder.java @@ -130,6 +130,10 @@ public Allowed isAddressAllowed(ServiceVPN vpn, Packet packet) { boolean fixTTLForPacket = isFixTTLForPacket(packet); + boolean dnsCryptIsRunning = modulesStatus.getDnsCryptState() == RUNNING + || modulesStatus.getDnsCryptState() == STARTING + || modulesStatus.getDnsCryptState() == RESTARTING; + lock.readLock().lock(); VpnPreferenceHolder vpnPreferences = vpn.vpnPreferences; @@ -156,12 +160,14 @@ public Allowed isAddressAllowed(ServiceVPN vpn, Packet packet) { } else if (!isSupported(packet.protocol)) { logw("Protocol not supported " + packet); } else if (packet.dport == DNS_OVER_TLS_PORT - && vpnPreferences.getIgnoreSystemDNS()) { + && vpnPreferences.getIgnoreSystemDNS() + && (dnsCryptIsRunning || torIsRunning)) { logw("Block DNS over TLS " + packet); } else if (vpnDnsSet.contains(packet.daddr) && packet.dport != PLAINTEXT_DNS_PORT && vpnPreferences.getIgnoreSystemDNS() - && packet.uid != vpnPreferences.getOwnUID()) { + && packet.uid != vpnPreferences.getOwnUID() + && (dnsCryptIsRunning || torIsRunning)) { logw("Block DNS over HTTPS " + packet); } else if (packet.uid == vpnPreferences.getOwnUID() || vpnPreferences.getCompatibilityMode() @@ -449,6 +455,7 @@ void prepareForwarding() { ModuleState dnsCryptState = modulesStatus.getDnsCryptState(); ModuleState torState = modulesStatus.getTorState(); ModuleState itpdState = modulesStatus.getItpdState(); + ModuleState firewallState = modulesStatus.getFirewallState(); int ownUID = pathVars.getAppUid(); @@ -469,24 +476,40 @@ void prepareForwarding() { //If Tor is ready and DNSCrypt is not, app will use Tor Exit node DNS in VPN mode if (dnsCryptState == RUNNING && (dnsCryptReady || !systemDNSAllowed)) { - addForwardPortRule(17, PLAINTEXT_DNS_PORT, LOOPBACK_ADDRESS, dnsCryptPort, ownUID); - addForwardPortRule(6, PLAINTEXT_DNS_PORT, LOOPBACK_ADDRESS, dnsCryptPort, ownUID); - + forwardDnsToDnsCrypt(dnsCryptPort, ownUID); if (itpdState == RUNNING) { - addForwardAddressRule(17, "10.191.0.1", LOOPBACK_ADDRESS, itpdHttpPort, ownUID); - addForwardAddressRule(6, "10.191.0.1", LOOPBACK_ADDRESS, itpdHttpPort, ownUID); + forwardAddressToITPD(itpdHttpPort, ownUID); } } else if (torState == RUNNING && (torReady || !systemDNSAllowed)) { - addForwardPortRule(17, PLAINTEXT_DNS_PORT, LOOPBACK_ADDRESS, torDNSPort, ownUID); - addForwardPortRule(6, PLAINTEXT_DNS_PORT, LOOPBACK_ADDRESS, torDNSPort, ownUID); + forwardDnsToTor(torDNSPort, ownUID); + } else if (dnsCryptState != STOPPED) { + forwardDnsToDnsCrypt(dnsCryptPort, ownUID); + } else if (torState != STOPPED) { + forwardDnsToTor(torDNSPort, ownUID); + } else if (firewallState == STARTING || firewallState == RUNNING) { + logi("Firewall only operation"); } else { - addForwardPortRule(17, PLAINTEXT_DNS_PORT, LOOPBACK_ADDRESS, dnsCryptPort, ownUID); - addForwardPortRule(6, PLAINTEXT_DNS_PORT, LOOPBACK_ADDRESS, dnsCryptPort, ownUID); + forwardDnsToDnsCrypt(dnsCryptPort, ownUID); } lock.writeLock().unlock(); } + private void forwardDnsToDnsCrypt(int dnsCryptPort, int ownUID) { + addForwardPortRule(17, PLAINTEXT_DNS_PORT, LOOPBACK_ADDRESS, dnsCryptPort, ownUID); + addForwardPortRule(6, PLAINTEXT_DNS_PORT, LOOPBACK_ADDRESS, dnsCryptPort, ownUID); + } + + private void forwardDnsToTor(int torDNSPort, int ownUID) { + addForwardPortRule(17, PLAINTEXT_DNS_PORT, LOOPBACK_ADDRESS, torDNSPort, ownUID); + addForwardPortRule(6, PLAINTEXT_DNS_PORT, LOOPBACK_ADDRESS, torDNSPort, ownUID); + } + + private void forwardAddressToITPD(int itpdHttpPort, int ownUID) { + addForwardAddressRule(17, "10.191.0.1", LOOPBACK_ADDRESS, itpdHttpPort, ownUID); + addForwardAddressRule(6, "10.191.0.1", LOOPBACK_ADDRESS, itpdHttpPort, ownUID); + } + @SuppressWarnings("SameParameterValue") private void addForwardPortRule(int protocol, int dport, String raddr, int rport, int ruid) { Forward fwd = new Forward(); diff --git a/tordnscrypt/src/main/jni/invizible/invizible.c b/tordnscrypt/src/main/jni/invizible/invizible.c index 638261041..708a7651a 100644 --- a/tordnscrypt/src/main/jni/invizible/invizible.c +++ b/tordnscrypt/src/main/jni/invizible/invizible.c @@ -205,6 +205,11 @@ Java_pan_alexander_tordnscrypt_vpn_service_ServiceVPN_jni_1stop( JNIEXPORT void JNICALL Java_pan_alexander_tordnscrypt_vpn_service_ServiceVPN_jni_1clear( JNIEnv *env, jobject instance, jlong context) { + + if (context == 0) { + return; + } + struct context *ctx = (struct context *) context; clear(ctx); } @@ -281,6 +286,11 @@ Java_pan_alexander_tordnscrypt_vpn_service_ServiceVPN_jni_1socks5_1for_1proxy(JN JNIEXPORT void JNICALL Java_pan_alexander_tordnscrypt_vpn_service_ServiceVPN_jni_1done( JNIEnv *env, jobject instance, jlong context) { + + if (context == 0) { + return; + } + struct context *ctx = (struct context *) context; log_android(ANDROID_LOG_INFO, "Done"); diff --git a/tordnscrypt/src/main/jni/invizible/udp.c b/tordnscrypt/src/main/jni/invizible/udp.c index 4c9ae8329..0ab271b7b 100644 --- a/tordnscrypt/src/main/jni/invizible/udp.c +++ b/tordnscrypt/src/main/jni/invizible/udp.c @@ -285,7 +285,7 @@ jboolean handle_udp(const struct arguments *args, return 0; } - if (ntohs(udphdr->dest) == 53 && redirect == NULL && uid != own_uid) { + if (ntohs(udphdr->dest) == 53 && redirect == NULL && uid != own_uid && args->fwd53) { log_android( ANDROID_LOG_ERROR, "Direct DNS connection for %s/%u to %s/%u uid %uid not allowed", source, ntohs(udphdr->source), dest, ntohs(udphdr->dest), uid diff --git a/tordnscrypt/src/main/res/drawable/ic_firewall_active_menu.xml b/tordnscrypt/src/main/res/drawable/ic_firewall_active_menu.xml new file mode 100644 index 000000000..581ef09b3 --- /dev/null +++ b/tordnscrypt/src/main/res/drawable/ic_firewall_active_menu.xml @@ -0,0 +1,32 @@ + + + + + + + diff --git a/tordnscrypt/src/main/res/drawable/ic_firewall_menu.xml b/tordnscrypt/src/main/res/drawable/ic_firewall_menu.xml new file mode 100644 index 000000000..5b16bf79e --- /dev/null +++ b/tordnscrypt/src/main/res/drawable/ic_firewall_menu.xml @@ -0,0 +1,32 @@ + + + + + + + diff --git a/tordnscrypt/src/main/res/drawable/ic_refresh.xml b/tordnscrypt/src/main/res/drawable/ic_refresh.xml new file mode 100644 index 000000000..43df87d0b --- /dev/null +++ b/tordnscrypt/src/main/res/drawable/ic_refresh.xml @@ -0,0 +1,24 @@ + + + + + + + diff --git a/tordnscrypt/src/main/res/drawable/ic_vpn_key.xml b/tordnscrypt/src/main/res/drawable/ic_vpn_key.xml new file mode 100644 index 000000000..90207d579 --- /dev/null +++ b/tordnscrypt/src/main/res/drawable/ic_vpn_key.xml @@ -0,0 +1,32 @@ + + + + + + + diff --git a/tordnscrypt/src/main/res/drawable/ic_vpn_key_white_24dp.xml b/tordnscrypt/src/main/res/drawable/ic_vpn_key_white_24dp.xml deleted file mode 100644 index 5d6835807..000000000 --- a/tordnscrypt/src/main/res/drawable/ic_vpn_key_white_24dp.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/tordnscrypt/src/main/res/layout-land/fragment_dnscrypt_run.xml b/tordnscrypt/src/main/res/layout-land/fragment_dnscrypt_run.xml index 61ef9b9f1..f8ce53446 100644 --- a/tordnscrypt/src/main/res/layout-land/fragment_dnscrypt_run.xml +++ b/tordnscrypt/src/main/res/layout-land/fragment_dnscrypt_run.xml @@ -31,13 +31,11 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="@dimen/status_text_margin" - android:fontFamily="serif" android:singleLine="true" android:text="@string/tvDNSStop" android:textAlignment="center" android:textColor="@color/textModuleStatusColorStopped" - android:textSize="@dimen/status_text_size" - android:typeface="monospace" /> + android:textSize="@dimen/status_text_size" /> + android:textSize="@dimen/status_text_size" /> + android:textSize="@dimen/status_text_size" /> + android:layout_height="150dp" + android:background="@color/ic_launcher_background"> + app:srcCompat="@drawable/gplv3" + app:tint="@color/colorWhite" /> + android:textAlignment="viewEnd" /> + android:textStyle="bold" /> + android:textAlignment="viewEnd" /> + android:textStyle="bold" /> @@ -109,8 +109,7 @@ android:layout_height="wrap_content" android:layout_weight="1" android:text="@string/about_build_ver" - android:textAlignment="viewEnd" - android:typeface="serif" /> + android:textAlignment="viewEnd" /> + android:textStyle="bold" /> @@ -138,8 +137,7 @@ android:layout_height="wrap_content" android:layout_weight="1" android:text="@string/about_proc_ver" - android:textAlignment="viewEnd" - android:typeface="serif" /> + android:textAlignment="viewEnd" /> + android:textStyle="bold" /> + android:textAlignment="viewEnd" /> + android:textStyle="bold" /> + android:textAlignment="center" /> + android:textColor="@color/ic_launcher_background" /> + android:textStyle="bold" /> + android:textStyle="bold" /> + android:text="@string/privacy_policy" /> + android:text="@string/about_own_licence" /> + android:textStyle="bold" /> + android:textColor="@color/colorAccent" /> + android:textColor="@color/colorAccent" /> + android:textColor="@color/colorAccent" /> + android:textColor="@color/colorAccent" /> + android:textColor="@color/colorAccent" /> + android:textColor="@color/colorAccent" /> + android:textColor="@color/colorAccent" /> + android:textColor="@color/colorAccent" /> diff --git a/tordnscrypt/src/main/res/layout/activity_help.xml b/tordnscrypt/src/main/res/layout/activity_help.xml index b39246a4e..6b3561424 100644 --- a/tordnscrypt/src/main/res/layout/activity_help.xml +++ b/tordnscrypt/src/main/res/layout/activity_help.xml @@ -66,10 +66,10 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/button_start_selector" + android:foreground="?attr/selectableItemBackgroundBorderless" android:minHeight="45dp" android:text="@string/help_activity_save_logs" - android:textColor="@color/buttonTextColor" - android:typeface="serif" /> + android:textColor="@color/buttonTextColor" /> + android:textAlignment="center" /> + android:textAlignment="center" /> - \ No newline at end of file + android:textAlignment="center" /> + diff --git a/tordnscrypt/src/main/res/layout/agreement_layout.xml b/tordnscrypt/src/main/res/layout/agreement_layout.xml index 5e797713f..59e940560 100644 --- a/tordnscrypt/src/main/res/layout/agreement_layout.xml +++ b/tordnscrypt/src/main/res/layout/agreement_layout.xml @@ -41,8 +41,7 @@ android:autoLink="all" android:linksClickable="true" android:padding="8dp" - android:text="@string/agreement_text" - android:typeface="serif" /> + android:text="@string/agreement_text" /> - - - - - - - - \ No newline at end of file diff --git a/tordnscrypt/src/main/res/layout/edit_text_for_dialog.xml b/tordnscrypt/src/main/res/layout/edit_text_for_dialog.xml index f9d63144e..a054dd048 100644 --- a/tordnscrypt/src/main/res/layout/edit_text_for_dialog.xml +++ b/tordnscrypt/src/main/res/layout/edit_text_for_dialog.xml @@ -12,6 +12,5 @@ android:ems="10" android:imeOptions="actionDone" android:inputType="text" - android:textAlignment="center" - android:typeface="serif" /> - \ No newline at end of file + android:textAlignment="center" /> + diff --git a/tordnscrypt/src/main/res/layout/fragment_backup.xml b/tordnscrypt/src/main/res/layout/fragment_backup.xml index ea9af167a..96c661f70 100644 --- a/tordnscrypt/src/main/res/layout/fragment_backup.xml +++ b/tordnscrypt/src/main/res/layout/fragment_backup.xml @@ -14,7 +14,7 @@ + android:orientation="vertical"> + android:textColor="@color/buttonTextColor" /> + android:textColor="@color/buttonTextColor" /> + android:textColor="@color/buttonTextColor" /> - \ No newline at end of file + diff --git a/tordnscrypt/src/main/res/layout/fragment_config_editor.xml b/tordnscrypt/src/main/res/layout/fragment_config_editor.xml index cdd275cf5..8073fa3bd 100644 --- a/tordnscrypt/src/main/res/layout/fragment_config_editor.xml +++ b/tordnscrypt/src/main/res/layout/fragment_config_editor.xml @@ -23,7 +23,6 @@ android:ems="10" android:focusable="true" android:focusableInTouchMode="true" - android:fontFamily="serif" android:gravity="start" android:imeOptions="actionDone" android:inputType="textLongMessage|textMultiLine|textNoSuggestions|text" @@ -33,4 +32,4 @@ - \ No newline at end of file + diff --git a/tordnscrypt/src/main/res/layout/fragment_dns_rule.xml b/tordnscrypt/src/main/res/layout/fragment_dns_rule.xml new file mode 100644 index 000000000..0fcbe4a24 --- /dev/null +++ b/tordnscrypt/src/main/res/layout/fragment_dns_rule.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + diff --git a/tordnscrypt/src/main/res/layout/fragment_show_rules_recycle.xml b/tordnscrypt/src/main/res/layout/fragment_itpd_subscription.xml similarity index 94% rename from tordnscrypt/src/main/res/layout/fragment_show_rules_recycle.xml rename to tordnscrypt/src/main/res/layout/fragment_itpd_subscription.xml index aba372d12..7c061d053 100644 --- a/tordnscrypt/src/main/res/layout/fragment_show_rules_recycle.xml +++ b/tordnscrypt/src/main/res/layout/fragment_itpd_subscription.xml @@ -5,7 +5,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" - tools:context=".settings.show_rules.ShowRulesRecycleFrag"> + tools:context=".settings.itpd_settings.ITPDSubscriptionsFragment"> - \ No newline at end of file + diff --git a/tordnscrypt/src/main/res/layout/fragment_preferences_tor_bridges.xml b/tordnscrypt/src/main/res/layout/fragment_preferences_tor_bridges.xml index 681302e42..8c486f2ca 100644 --- a/tordnscrypt/src/main/res/layout/fragment_preferences_tor_bridges.xml +++ b/tordnscrypt/src/main/res/layout/fragment_preferences_tor_bridges.xml @@ -21,134 +21,137 @@ - - + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:checked="true" + android:fontFamily="sans-serif-thin" + android:text="@string/pref_fast_use_tor_bridges_no" + android:textSize="15sp" + android:textStyle="bold" /> + + + - - - - - - + + - - - - - - - - - - - + + - - - - - - - + android:layout_height="match_parent" + android:layout_weight="3" + android:dropDownWidth="wrap_content" + android:entries="@array/tor_bridges_default_obfs" + android:spinnerMode="dialog" /> + + + + + - - - + + + android:layout_weight="3" + android:entries="@array/tor_bridges_own_obfs" + android:spinnerMode="dialog" /> - + + + + + + diff --git a/tordnscrypt/src/main/res/layout/fragment_show_log.xml b/tordnscrypt/src/main/res/layout/fragment_show_log.xml index f38fef43e..5f1c02de3 100644 --- a/tordnscrypt/src/main/res/layout/fragment_show_log.xml +++ b/tordnscrypt/src/main/res/layout/fragment_show_log.xml @@ -31,7 +31,7 @@ android:layout_height="wrap_content" android:textIsSelectable="true" android:textSize="@dimen/fragment_file_log_text_size" - android:typeface="serif" /> + tools:text="@tools:sample/lorem/random" /> @@ -45,10 +45,9 @@ android:adjustViewBounds="true" android:cropToPadding="true" android:fitsSystemWindows="false" - app:srcCompat="@drawable/ic_clear_white_24dp" app:backgroundTint="@color/buttonColor" app:fabSize="auto" - app:rippleColor="@color/colorAccent" /> + app:rippleColor="@color/colorAccent" + app:srcCompat="@drawable/ic_clear_white_24dp" /> - - \ No newline at end of file + diff --git a/tordnscrypt/src/main/res/layout/item_bridge.xml b/tordnscrypt/src/main/res/layout/item_bridge.xml index f5c5c4206..c7ff2dd77 100644 --- a/tordnscrypt/src/main/res/layout/item_bridge.xml +++ b/tordnscrypt/src/main/res/layout/item_bridge.xml @@ -29,12 +29,13 @@ android:id="@+id/tvBridgeCountry" android:layout_width="wrap_content" android:layout_height="match_parent" + android:fontFamily="sans-serif-thin" + android:gravity="center" android:paddingStart="8dp" android:paddingEnd="8dp" - android:fontFamily="serif" - android:gravity="center" android:textAlignment="gravity" - tools:text = "EN"/> + android:textStyle="bold" + tools:text="EN" /> + tools:text="obfs4 100.100.100.100:1234" /> + android:textStyle="bold" + tools:text="100 ms" /> + + + + + diff --git a/tordnscrypt/src/main/res/layout/item_dns_relay.xml b/tordnscrypt/src/main/res/layout/item_dns_relay.xml index d35f35b76..2584238bc 100644 --- a/tordnscrypt/src/main/res/layout/item_dns_relay.xml +++ b/tordnscrypt/src/main/res/layout/item_dns_relay.xml @@ -34,11 +34,10 @@ android:layout_height="wrap_content" android:layout_marginEnd="8dp" android:layout_weight="1" - android:fontFamily="serif" + android:fontFamily="sans-serif-medium" android:singleLine="true" android:textAlignment="center" - android:textSize="16sp" - android:textStyle="bold" /> + android:textSize="16sp" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tordnscrypt/src/main/res/layout/item_dns_server.xml b/tordnscrypt/src/main/res/layout/item_dns_server.xml index 8b7ff3b3a..7521481b3 100644 --- a/tordnscrypt/src/main/res/layout/item_dns_server.xml +++ b/tordnscrypt/src/main/res/layout/item_dns_server.xml @@ -40,11 +40,11 @@ android:layout_height="wrap_content" android:layout_marginEnd="8dp" android:layout_weight="1" - android:fontFamily="serif" + android:fontFamily="sans-serif-medium" android:singleLine="true" android:textAlignment="center" android:textSize="16sp" - android:textStyle="bold" /> + tools:text="fgsfgsg.com" /> + app:layout_constraintTop_toTopOf="parent" + tools:text="www.google.com" /> + tools:visibility="visible" /> @@ -72,4 +74,4 @@ android:scaleType="fitCenter" app:srcCompat="@android:drawable/ic_menu_delete" /> - \ No newline at end of file + diff --git a/tordnscrypt/src/main/res/layout/item_tor_app.xml b/tordnscrypt/src/main/res/layout/item_tor_app.xml index d09d7c57d..f71cdf0a8 100644 --- a/tordnscrypt/src/main/res/layout/item_tor_app.xml +++ b/tordnscrypt/src/main/res/layout/item_tor_app.xml @@ -61,7 +61,6 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_margin="4dp" - android:fontFamily="serif" android:singleLine="false" android:textAlignment="center" android:textStyle="bold" @@ -76,7 +75,6 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_margin="4dp" - android:fontFamily="serif" android:singleLine="true" android:textAlignment="center" app:layout_constraintBottom_toBottomOf="parent" diff --git a/tordnscrypt/src/main/res/layout/item_tor_ips.xml b/tordnscrypt/src/main/res/layout/item_tor_ips.xml index 594c1d14f..37bc5fb16 100644 --- a/tordnscrypt/src/main/res/layout/item_tor_ips.xml +++ b/tordnscrypt/src/main/res/layout/item_tor_ips.xml @@ -40,9 +40,8 @@ android:layout_height="wrap_content" android:layout_margin="4dp" android:layout_weight="1" - android:fontFamily="serif" + android:fontFamily="sans-serif-medium" android:gravity="fill" - android:textStyle="bold" tools:ignore="NestedWeights" /> @@ -86,9 +85,9 @@ android:layout_gravity="bottom|end" android:layout_marginEnd="3dp" android:background="@drawable/button_item_selector" + android:clickable="true" android:contentDescription="Delete" android:focusable="true" - android:clickable="true" android:scaleType="fitCenter" app:srcCompat="@android:drawable/ic_menu_delete" /> diff --git a/tordnscrypt/src/main/res/layout/main_fragment.xml b/tordnscrypt/src/main/res/layout/main_fragment.xml index 68bba8936..6b0640636 100644 --- a/tordnscrypt/src/main/res/layout/main_fragment.xml +++ b/tordnscrypt/src/main/res/layout/main_fragment.xml @@ -126,7 +126,8 @@ android:layout_marginBottom="4dp" android:layout_weight="1" android:gravity="center" - android:text="@string/tvTorStop" /> + android:text="@string/tvTorStop" + android:textSize="15sp" /> + android:text="@string/tvDNSStop" + android:textSize="15sp" /> + android:text="@string/tvITPDStop" + android:textSize="15sp" /> - \ No newline at end of file + diff --git a/tordnscrypt/src/main/res/menu/main.xml b/tordnscrypt/src/main/res/menu/main.xml index 1e654c5a8..75adefc7c 100644 --- a/tordnscrypt/src/main/res/menu/main.xml +++ b/tordnscrypt/src/main/res/menu/main.xml @@ -2,46 +2,52 @@ + + android:orderInCategory="6"> diff --git a/tordnscrypt/src/main/res/values-bg/strings.xml b/tordnscrypt/src/main/res/values-bg/strings.xml index 3485f208c..d2d3a64b5 100644 --- a/tordnscrypt/src/main/res/values-bg/strings.xml +++ b/tordnscrypt/src/main/res/values-bg/strings.xml @@ -1,108 +1,108 @@ най-доброто решение за вашата поверителност - I2P спиране - I2P инсталиране - I2P е инсталиран - Стартирайте при зареждане + I2P Спиране + I2P Инсталиране + I2P Инсталиран + Стартирай при зареждане Проверява се дали Root е наличен… Премиум версия Съгласен съм Не съм съгласен - Запазите промените + Запази промените Отхвърли промените Откажи Попитайте по-късно Можете да използвате InviZible Pro с приложения със собствен прокси или локална VPN функция в прокси режим. - Запазване на файла Грешка! + Запази файла Грешка! Общи настройки - DNSCrypt настройки - Tor настройки - I2P настройки - Бързи настройки + DNSCrypt Настройки + Tor Настройки + I2P Настройки + Бързи Настройки Архивиране и Възстановяване Относно Дарете Възстановяване на настройките - Запазване на настройките - Път на папката: - Изберете папка за архивиране: + Запази настройките + Път на папка: + Избери папка за архивиране: Резервното копие е запазено Автоматично стартиране - Стартирайте Tor при зареждане - Закъснение + Автоматично стартиране Tor + Забавяне DNSCrypt сървъри - Изберете DNSCrypt сървъри + Избери DNSCrypt сървъри Tor с DNSCrypt и I2P Настройки - I2P спрян - I2P работи - I2P стартира - I2P не е инсталиран + I2P Спряно + I2P Работи + I2P Стартира + I2P Не е инсталиран DNSCrypt Инсталиран DNSCrypt Инсталиране Готово DNSCrypt Не е инсталиран DNSCrypt Стартира - DNSCrypt спира - Tor е инсталиран + DNSCrypt Спиране + Tor Инсталиран Нещо се обърка! DNSCrypt Работи Искате ли да завършите инсталацията? Позволи - Тор спря + Тор Спряно Сигурен ли си? Това действие не може да бъде отменено! - Tor се инсталира - Tor не е инсталиран - Tor стартира + Tor Инсталиране + Tor Не е инсталиран + Tor Стартиране Свързване - Tor работи - Tor спиране + Tor Работи + Tor Спиране Отказ Грешка! Внимание! Инсталирай Моля изчакай… - Не питай - DNSCrypt Спрян - Tor настройки + Не показвай + DNSCrypt Спряно + Tor Настройки Настройки за маршрутизиране - Маршрут на целия трафик през Tor - Насочете целия трафик през InviZible - Изберете Уеб сайтове + Насочи целия трафик през Tor + Насочи целия трафик през InviZible + Избери сайтове Логове - Изберете Приложения - Нулиране на настройките + Избери приложения + Нулирай настройките Резервното копие е възстановено Настройки - Стартирайте DNSCrypt при зареждане - Стартирайте I2P при зареждане + Автоматично стартиране DNSCrypt + Автоматично стартиране I2P Изход Можете да използвате InviZible Pro с локален VPN режим или приложения със собствен прокси или локална VPN функция в прокси режим. - Използвайте забавяне (секунди) само ако автоматичното стартиране не работи правилно - Списък с уеб сайтове за отваряне с Tor. Тази функция не работи за сайтове зад CDN + Използвай забавяне (секунди) само ако автоматичното стартиране не работи правилно + Списък със сайтове за отваряне с Tor. Тази функция не работи за сайтове зад CDN Списък с приложения за използване с InviZible - Изключване на уеб сайтове - Списък с уеб сайтове за директно отваряне. Тази функция не работи за сайтове зад CDN - Изключване на приложения - Период в часове за опресняване на IP адресите на уеб сайтове. За Android 5.1 и по-нова версия. Поставете 0, за да спрете опресняването. - Използвайте го, ако не можете да се свържете с Tor мрежа + Изключи сайтове + Списък със сайтове за директно отваряне. Тази функция не работи за сайтове зад CDN + Изключи приложения + Период в часове за опресняване на IP адреса на сайта. За Android 5.1 и по-нова версия. Поставете 0, за да спрете опресняването. + Използвай, ако не можете да се свържете с Tor мрежа Моля, деактивирайте Entry Nodes в настройките на Tor, в противен случай не можете да използвате Bridges. Заявка за Bridges - Налични са нови Tor мостове по подразбиране. Искате ли да ги актуализирате? - Изберете Тема + Налични са нови Tor bridges по подразбиране. Искате ли да ги актуализирате? + Избери тема Автоматичен Система по подразбиране Разрешаване на I2P Tethering - Отказ на връзка към http порт 80 - Проверете дали вече са налични нови версии + Отказ на връзка към http port 80 + Провери дали вече са налични нови версии Откриване на ARP подправяне - Проверявайте актуализациите само с Tor и когато Tor работи - Използвайте Root привилегии за DNSCrypt, Tor и I2P модули. Активирането на тази функция оставя модулите неуправлявани и може да причини проблеми с връзката! + Проверявай актуализациите само с Tor и когато Tor работи + Използвай Root привилегии за DNSCrypt, Tor и I2P модули. Активирането на тази функция оставя модулите неуправлявани и може да причини проблеми с връзката! Други - Блокирайте HOTSPOT http - Отказ на връзка към http порт 80 за HOTSPOT - Защитете приложението с режим без Root, за да предотвратите спиране на приложението от android + Блокирай HOTSPOT http + Отказ на връзка към http port 80 за HOTSPOT + Защити приложение да бъде убито от android Откриване на фалшиви DHCP атаки Man-in-the-middle в Wi-Fi мрежи. Блокирайте интернет връзката когато Tor, DNSCrypt и Purple I2P са спрени Актуализирайте правилата при всяка промяна на свързаността @@ -150,22 +150,22 @@ Използвайте приложението iptables Списък с приложения за използване с Tor Списък с приложения за директно отваряне - Изключете от Tor - Маршрут до Tor - Изключете UDP от Tor + Изключи от Tor + Насочи към Tor + Изключи UDP от Tor Интервал на опресняване - Мостове - Приложение за заобикаляне - Не използвайте мостове - Използвайте списъка по подразбиране на Bridges - Използвайте собствен списък за Bridges - Объркване - Добавяне на Bridges - Редактиране на Bridge - Моля, изберете тип замъгляване: + Bridges + Изключи напълно + Не използвай Bridges + Използвай Bridges списъка по подразбиране + Използвай Bridges собствен списък + Замаскиране + Добави Bridges + Редактирай Bridge + Моля, изберете тип замаскиране: Моля, въведете знаци от изображението - Искане на нови мостове - Вашите нови мостове от bridges.torproject.org: + Заявка за нови Bridges + Вашите нови bridges от bridges.torproject.org: Запази Затвори Грешно име на хост! @@ -174,17 +174,17 @@ Заобикаляне на LAN адреси Избери език Дневници в реално време - Показване на регистрационни файлове за връзка с приложението в раздела DNS - Блокиране на http + Покажи регистрационни файлове за връзка с приложението в раздела DNS + Блокирай http Актуализация Автоматични актуализации - Проверявайте ежедневно актуализациите на InviZible Pro и модулите + Проверявай ежедневно актуализациите на InviZible Pro и модулите Проверка за актуализация - Актуализирайте стриктно през Tor - Използвайте Root привилегии - Стартирайте модули с Root + Актуализирай стриктно през Tor + Използвай Root привилегии + Стартирай модули с Root Откриване на MITM атака - Откриване на Man-in-the-middle ARP подправяне и измамни DHCP атаки в Wi-Fi мрежи. + Откриване на Man-in-the-Middle ARP спуфинг и измамни DHCP атаки в Wi-Fi мрежи. Измамно откриване на DHCP Блокирайте интернет, когато бъде открита атака Интернет връзката ще бъде блокирана по време на атака @@ -251,7 +251,7 @@ Активиране на подозрително регистриране Регистрирайте заявки за несъществуващи зони. Тези заявки могат да разкрият наличието на зловреден софтуер, повредени/остарели приложения и устройства, сигнализиращи за присъствието си на трети страни. Отворете подозрителен дневник - Измислица SNI + Фалшив SNI Блокиране въз основа на модел (черен списък) Черен списък Черният списък е съставен от един модел на ред. diff --git a/tordnscrypt/src/main/res/values-de/strings.xml b/tordnscrypt/src/main/res/values-de/strings.xml index 1d22dfc67..8354c4eac 100644 --- a/tordnscrypt/src/main/res/values-de/strings.xml +++ b/tordnscrypt/src/main/res/values-de/strings.xml @@ -75,11 +75,11 @@ Liste der Apps, die mit Tor geöffnet werden Liste der Apps, die mit InviZible geöffnet werden Seiten ausschließen - Websites, die direkt geöffnet werden. Dies funktioniert nicht für Seiten hinter CDNs. + Liste der Webseiten, die direkt geöffnet werden. Dies funktioniert nicht für Seiten hinter CDNs Anwendungen ausschließen Liste der Apps, die direkt geöffnet werden Aktualisierungsintervall - Zeitraum in Stunden, um Website-IPs zu aktualisieren. Für Android 5.1 und höher. Setze auf 0, um die Aktualisierungen zu stoppen. + Zeitraum in Stunden, um Webseiten-IPs zu aktualisieren. Für Android 5.1 und höher. Setze auf 0, um die Aktualisierungen zu stoppen. Brücken Benutze diese, falls du dich nicht mit dem Tor-Netzwerk verbinden kannst Brücken nicht benutzen @@ -118,10 +118,10 @@ "Aktualisierung der Regeln" "Aktualisiere die Regeln bei jeder Verbindungsänderung" "Zeige Benachrichtigungen" - Verhindere das automatische Ausschalten des Bildschirms + Verhindere einen Wechsel in einen Ruhezustand Untersage dem Gerät in einen Ruhezustand zu wechseln. Kann bei Verwendung der HOTSPOT-Funktion nützlich sein. Verursacht erhöhten Akkuverbrauch! Hilfe-Nachrichten - Alle Nachrichten anzeigen, die als nicht zum Anzeigen erlaubt markiert wurden. + Alle Nachrichten anzeigen, die als nicht zum Anzeigen erlaubt markiert wurden Bitte deaktiviere die Batterieoptimierung deines Telefons für InviZible Pro. Andernfalls können DNSCrypt, Tor oder I2P jederzeit durch Android beendet werden. Auf manchen Systemen, wie zB MIUI, können zusätzliche Schritte nötig sein. "BusyBox auswählen" "HOTSPOT-Experimentell" @@ -149,18 +149,18 @@ "Nur für fortgeschrittene Benutzer!" "Wähle mindestens einen Server!" Lokaler Port, auf dem gelauscht wird. - Benötige Server (von statischen + Remote-Quellen), um spezielle Eigenschaften zu erfüllen. + Server (von statischen + Remote-Quellen), müssen spezielle Eigenschaften erfüllen Benutze Server, die das DNSCrypt-Protokoll implementieren. Benutze Server, die das DNS-over-HTTPS-Protokoll implementieren. Benötige Server, die von Remote-Quellen definiert wurden, um spezielle Eigenschaften zu erfüllen Server muss die DNS-Sicherheitserweiterungen unterstützen (DNSSEC). "Server darf Useranfragen nicht speichern (deklarativ)." "Server darf keine eigene Blacklist erzwingen (Elterliche Kontrolle, Blockieren von Werbung…)." - Benutze immer TCP zum Verbinden mit Upstream-Servern. - "Dies kann nützlich sein, wenn jeglicher Datenverkehr durch Tor geleitet werden soll. Andernfalls sollte dies nicht aktiviert werden, da die Sicherheit dadurch nicht verbessert, die Latenz jedoch erhöht wird (DNSCrypt-Proxy wird immer alles verschlüsseln, auch wenn UDP verwendet wird)." + Benutze immer TCP zum Verbinden mit Upstream-Servern + Verwende TCP anstelle von UDP um zu DNSCrypt-Servern zu verbinden. Diese Option sollte aktiviert sein, falls du DNSCrypt in Kombination mit Tor verwendest. SOCKS-Proxy - "Proxy aktivieren" - Route alle TCP-Verbindungen zu einem lokalen Tor-Knoten. Da Tor UDP nicht unterstützt, setze die Einstellung „TCP erzwingen“ ebenfalls auf aktiv. + Ausgehender Proxy + Route alle TCP-Verbindungen zu einem lokalen Tor Socks5-Proxy. Da Tor UDP nicht unterstützt, setze die Einstellung „Force TCP“ ebenfalls auf aktiv. Proxy-Port Andere Einstellungen Bootstrap-Resolver. Dies ist ein normaler, unverschlüsselter DNS-Resolver, der nur für einmalige Anfragen benutzt wird, um die Liste von Eingangsresolvern zu beziehen, und dies nur, wenn die System-DNS-Konfiguration nicht funktioniert. Er wird nicht mehr benutzt, sobald sich Listen im Cache befinden. @@ -170,31 +170,31 @@ "Route Anfragen für spezifische Domains zu festgelegten Servern weiter." Cloaking-Regeln Cloaking gibt für spezifische Namen eine festgelegte Adresse zurück. Zusätzlich zur Funktion als HOSTS-Datei kann Cloaking auch die IP-Adresse eines anderen Namens zurückgeben. CNAME-Flattening wird ebenso durchgeführt. - "Logging von Anfragen." + Logging von Anfragen "Logge Client-Anfragen in eine Datei." - "Aktiviere das Logging von Anfragen" + Abfrage-Protokollierung Folgende Anfragetypen nicht loggen, um die Informationsmenge zu reduzieren. Leerlassen, um alles zu loggen. Öffne das Anfragen-Log - "Logging von verdächtigen Anfragen." + Logging von verdächtigen Anfragen "Logge Anfragen für nichtexistente Zonen. Die Anfragen können das Vorhandensein von Malware, kaputten Applikationen oder Geräten an Dritte melden bzw aufdecken.." Aktiviere verdächtiges Logging Öffne das Verdächtigen-Log - Schema-gestütztes Blocking (Blacklist). + Schema-gestütztes Blocking (Blacklist) "Blacklist" "Blacklists bestehen aus einem Schema pro Zeile." - Schema-gestütztes IP-Blocking (IP-Blacklist). + Schema-gestütztes IP-Blocking (IP-Blacklist) IP-Blacklist IP-Blacklists bestehen aus einem Schema pro Zeile. - Schema-gestütztes Whitelisting (Blacklist-Umgehung). + Schema-gestütztes Whitelisting (Blacklist-Umgehung) "Whitelist" Whitelists unterstützen die gleichen Schemata wie Blacklists. Wenn sich ein Name in der Whitelist befindet, umgeht diese Sitzung Namens- und IP-Filter. "Servers" "Quellen" Remote-Liste von verfügbaren Servern. - "Quellenliste wird nach refresh_delay Stunden ungültig." + Serverliste wird nach der in Stunden angegebenen Verzögerung aktualisiert. "Relays" "Quellen" - "Relayliste wird nach refresh_delay Stunden ungültig." + Relayliste wird nach der in Stunden angegebenen Verzögerung aktualisiert. DNSCrypt-Relays "Voreinstellung existiert nicht in dnscrypt-proxy.toml!" "Log löschen" @@ -213,7 +213,7 @@ "Eine Liste von Ländercodes für Knoten, die für den Einstieg in deinen normalen Circuit verwendet werden." Wenn StrictNodes aktiviert ist, wird Tor ausschließlich die ExcludeNodes-Option als Vorraussetzung für alle generierten Circuits heranziehen, selbst wenn dies für dich die Funktionalität beeinträchtigen sollte. "Wenn aktiviert, dann wird Tor nur Verbindungen zu ORs aufbauen, die über deine Firewall erreichbar sind (Standard 80 und 443)." - Überlege alle NUM Sekunden, ob ein neuer Circuit aufgebaut werden soll + Überlege alle NUM Sekunden, ob ein neuer Circuit aufgebaut werden soll. Erlaube, einen Circuit wiederzuverwenden, der mindestens NUM Sekunden zuvor erstmalig benutzt wurde, aber füge nie einen neuen Stream einem veralteten Circuit hinzu. Falls aktiviert, wird Tor nie zwei Server, deren IPs zu nahe beinander sind, in denselben Circuit aufnehmen. Momentan sind sich zwei Adressen zu nahe, wenn sie sich im gleichen /16-Bereich befinden. SOCKS-Proxy @@ -226,7 +226,7 @@ Öffne diesen Port, um auf UDP-DNS-Anfragen zu lauschen und sie anonym aufzulösen. Wenn deaktiviert, wird Tor kein IPv4 nutzen, um sich mit Verzeichnisservern oder Einstiegsknoten zu verbinden. Wenn aktiviert, kann Tor versuchen, sich via IPv6 mit Verzeichnisservern oder Einstiegsknoten zu verbinden. - "Voreinstellung existiert nicht in dnscrypt-proxy.toml!" + Voreinstellung existiert nicht in tor.conf! Host oder IP zum Entsperren Host oder IP zum Umgehen "Bearbeite Host oder IP" @@ -239,7 +239,7 @@ Allgemeine Einstellungen Eingehende Verbindungen Port, auf dem auf eingehende Verbindungen gelauscht wird. - Externe IP des Routers für eingehende Verbindungen. + Externe IP des Routers für eingehende Verbindungen (Standard: Automatisch, falls SSU2 aktiviert ist). "Aktiviere Kommunikation über IPv4." "Aktiviere Kommunikation über IPv6." Router akzeptiert keine Transit-Tunnel und deaktiviert somit Transit-Verkehr komplett. @@ -306,7 +306,7 @@ Tor-Aktualisierung ist verfügbar. Möchtest du sie herunterladen und installieren? Die Installation läuft im Hintergrund weiter. Purple-2P-Aktualisierung ist verfügbar. Möchtest du sie herunterladen und installieren? Die Installation läuft im Hintergrund weiter. Prüfen auf Aktualisierungen - Bitte warte, während auf Aktualisierungen geprüft wird + Bitte warte, während auf Aktualisierungen geprüft wird. Der Update-Server ist temporär nicht erreichbar. Bitte versuche es später noch einmal. Der Update-Server war nicht erreichbar. Fehler beim Aktualisieren. @@ -363,7 +363,7 @@ Benutze Server, die über IPv4 erreichbar sind Benutze Server, die über IPv6 erreichbar sind Importiere Weiterleitungsregeln - Importiere eine Weirerleitungs-Regeldatei. Du kannst mehrere Dateien auswählen, doppelte Einträge werden entfernt. + Importiere eine Weiterleitungs-Regeldatei. Du kannst mehrere Dateien auswählen, doppelte Einträge werden entfernt. Lösche Weiterleitungsregeln Importiere Cloaking-Regeln Importiere eine Cloaking-Regeldatei. Du kannst mehrere Dateien auswählen, doppelte Einträge werden entfernt. @@ -405,7 +405,7 @@ Unterstützung für mehrere Nutzer Unterstützung für Dual-Apps, MIUI, Island, Shelter und Apps mit Arbeitsprofilen SOCKS-Ausgabe-Proxy - Tor wird alle OR-Verbindungen durch den SOCKS5-Proxy leiten + Tor wird alle OR-Verbindungen durch den SOCKS5-Proxy leiten. "Laufende Dienste" Aktualisierungsbenachrichtigungen Root-Kommandos-Benachrichtigung @@ -428,11 +428,11 @@ "Erfolgreiche Verbindung. Ping %s ms." ARP-Spoofing erkannt! "Bösartiges DHCP erkannt!" - Man-in-the-middle-Angriff erkannt! Deine Daten können von einem anderen Gerät im lokalen Netzwerk abgefangen werden. WLAN aus- und kurz darauf wieder anschalten. Die Verwendung des derzeitigen Hotspots kann ebenfalls betroffen sein. + Man-in-the-middle-Angriff erkannt! Deine Daten können von einem anderen Gerät im lokalen Netzwerk abgefangen werden. WLAN aus- und kurz darauf wieder anschalten. Die Verwendung des derzeitigen Hotspots kann ebenfalls betroffen sein! Erkennung von ARP-Spoofing auf deinem Gerät wird leider nicht unterstützt! Bitte deaktiviere die Einstiegsknoten in den Tor-Einstellungen, um Brücken nutzen zu können. Umgehe LAN-Adressen - Benutze Tor nicht für Ziele im LAN und IANA-reservierte IP-Blöcke. + Benutze Tor nicht für Ziele im LAN und IANA-reservierte IP-Blöcke Deaktiviere Tor-Brücken, wenn du Einstiegsknoten verwenden möchtest. Firewall-Benachrichtigungen "Firewall" @@ -462,23 +462,23 @@ Grün – Erlauben, weiß – Verweigern von Verbindungen Firewall-Einstellungen "Voreinstellung für neue Apps" - Erlaube Internetverbindungen für neu installierte Apps - Blockiere Internetverbindungen für neu installierte Apps + Erlaube Internetverbindungen für neu installierte Apps. + Blockiere Internetverbindungen für neu installierte Apps. Bitte deaktiviere den Proxy in den Android-WLAN-Netzwerkeinstellungen. Diese Option beeinträchtigt InviZible. Bitte deaktiviere den Proxy in den Android-GSM-APN-Einstellungen. Diese Option beeinträchtigt InviZible. "Hilfsbenachrichtigungen" DNS-Rebinding-Schutz Blockiere Seite, wenn eine DNS-Rebinding-Attacke erkannt wird DNS-Rebinding - Potentielle DNS-Rebinding-Attacke erkannt! Seite %s wurde blockiert + Potentielle DNS-Rebinding-Attacke erkannt! Seite %s wurde blockiert. "Bist du sicher? Diese Aktion kann nicht rückgängig gemacht werden!" "Einstellungen zurücksetzen" "Backup gespeichert" "Backup wiederhergestellt" - Das InviZible-Pro-Projekt und sein Entwickler möchten sich für deine Unterstützung bedanken. - Versuche, die eingebaute Kryptografie-Hardwarebeschleunigung zu nutzen, falls verfügbar - "Verbinde..." - "Warten auf Verbindung..." + Das InviZible-Pro-Projekt und sein Entwickler möchten sich für deine Unterstützung bedanken! + Versuche, die eingebaute Kryptografie-Hardwarebeschleunigung zu nutzen, falls verfügbar. + Verbinde… + Warten auf Verbindung… Wechsle Tor-IP Tor-IP wird gewechselt "Du hast mehr als 3 Kacheln zu den Schnelleinstellungen hinzugefügt. Bitte belasse es bei 3. Das Hinzufügen von mehr als 3 Kacheln kann Probleme verursachen." @@ -490,14 +490,14 @@ Apps werden unabhängig von ihrem Internetstatus angezeigt. Nur Apps mit Internetberechtigung werden angezeigt. "Aus-Schalter" - Blockiere die Internetverbindung, sobald Tor, DNSCrypt oder I2P gestoppt wurden. + Blockiere die Internetverbindung, sobald Tor, DNSCrypt oder I2P gestoppt wurden Die Internetverbindung wurde aufgrund des Aus-Schalters blockiert. Starte Tor, DNSCrypt oder I2P, um sie zu reaktivieren. Alternativ kann der Aus-Schalter in den Einstellungen deaktiviert werden. "Liste ist leer" "Automatisch" "BusyBox des Gerätes verwenden" BusyBox des Programms verwenden "BusyBox nicht verwenden" - "IPTables des Gerätes verwenden" + IPTables der Applikation verwenden "IPTables des Gerätes verwenden" Warten auf die xTables-Sperre Warten, bis die IPTables-Sperre greift, um gleichzeitige Änderung von IPTables-Regeln zu verhindern. @@ -508,7 +508,7 @@ SSU2-Transportprotokoll aktivieren (benutze UDP). "Später nachfragen" Benachrichtigungen sind essenziell, um wichtige Informationen über App-Vorgänge, den App-Status und entdeckte Angriffe anzuzeigen. Möchtest du Benachrichtigungen für InviZible erlauben? - "Aktiviere diese Option nur, wenn du dich in einem IPv6-Netzwerk befindest und IPv4-Adressen nicht mehr erreichbar sind. Andernfalls wirst du keinerlei Verbindungen mehr aufbauen können." + Aktiviere diese Option nur, wenn du dich in einem IPv6-Netzwerk befindest und IPv4-Adressen nicht mehr erreichbar sind. Andernfalls wirst du keinerlei Verbindungen mehr aufbauen können. Verwendete statische IPv6-Präfixe. IPv6-Brücken "Achtung" @@ -538,5 +538,14 @@ Teile Circuits nicht mit Streams anderer Apps. Diese System-App zu blockieren, kann zu einer instabilen Internetverbindung führen! Um diese Option zu aktivieren, solltest du Brücken deaktivieren oder Brücken lediglich mit den Ports 80 und 443 verwenden. - Server verwenden, die das Oblivious-DNS-over-HTTPS-Protokoll implementieren. + Benutze Server, die das Oblivious-DNS-over-HTTPS-Protokoll implementieren. + Addresse des ausgehenden Proxys (IP oder lokal). Anfragen, die nicht I2P betreffen, gehen dorthin. + Ausgehende HTTP-Proxy Adresse (z.B. http://false.i2p) + URL hinzufügen + Regel hinzufügen + Regeln + Insgesamt: %d Regeln + Lokale Liste hinzufügen + Regeln werden nach der in Stunden angegebenen Verzögerung aktualisiert. + Remote Liste hinzufügen diff --git a/tordnscrypt/src/main/res/values-el/strings.xml b/tordnscrypt/src/main/res/values-el/strings.xml index c57fc3f46..f84604b1d 100644 --- a/tordnscrypt/src/main/res/values-el/strings.xml +++ b/tordnscrypt/src/main/res/values-el/strings.xml @@ -13,7 +13,7 @@ "Εναρξη κατά την εκκίνηση" "Έλεγχος εάν η πρόσβαση Root είναι διαθέσιμη…" "Παρακαλώ περιμένετε…" - Να μην εμφανιστεί πάλι + Μην εμφανιστεί πάλι "DNSCrypt Ανενεργό" "DNSCrypt Εγκατεστημένο" "Εγκατάσταση DNSCrypt" @@ -64,8 +64,8 @@ Should it be named instead "Βασικές Ρυθμίσεις" (originating from "Επιλογή φακέλου:" "Ρυθμίσεις" "Αυτόματη Εκκίνηση" - Εκκίνηση DNSCrypt κατά τη φόρτωση - Εκκίνηση Tor κατά τη φόρτωση + Εκκίνηση DNSCrypt κατά την εκκίνηση + Εκκίνηση Tor κατά την εκκίνηση Εναρξη I2P κατά την εκκίνηση "Καθυστέρηση" "Χρησιμοποιήστε την καθυστέρηση (σε δευτερόλεπτα) μόνο εάν η αυτόματη εκκίνηση δεν λειτουργεί σωστά" @@ -81,17 +81,17 @@ Should it be named instead "Βασικές Ρυθμίσεις" (originating from "Λίστα εφαρμογών για χρήση με το Tor" "Λίστα εφαρμογών για χρήση με το InviZible" Εξαίρεση Ιστοσελίδων - Λίστα ιστοσελιδών για απευθείας άνοιγμα. Αυτή η δυνατότητα δεν λειτουργεί για ιστότοπους που βρίσκονται πίσω από το CDN δίκτυο - Εξαίρεση Εφαρμογών + Λίστα ιστοσελίδων για απευθείας άνοιγμα. Αυτή η δυνατότητα δεν λειτουργεί για ιστότοπους που βρίσκονται πίσω από CDN δίκτυο + Εξαίρεση εφαρμογών "Λίστα εφαρμογών που ανοίγουν απευθείας" Διάστημα ανανέωσης Περίοδος σε ώρες για την ανανέωση των IP Ιστοτόπων. Για Android 5.1 και νεότερες εκδόσεις. Βάλτε 0 για να σταματήσει η ανανέωση. - "Επικαλύψεις" + Γέφυρες "Χρησιμοποιήστε το εάν δεν μπορείτε να συνδεθείτε στο δίκτυο Tor" - "Χωρίς Χρήση Επικαλύψεων" - "Χρήση Προκαθορισμένης Λίστας Επικαλύψεων" + Χωρίς Χρήση Γεφύρων + Χρήση Προκαθορισμένης Λίστας Γεφύρων "Χρήση Λίστας Επικαλύψεων απο χρήστη" "Αίτηση Επικαλύψεων" @@ -107,76 +107,76 @@ Should it be named instead "Βασικές Ρυθμίσεις" (originating from "Αποθήκευση" "Κλείσιμο" "Λάθος όνομα διακομιστή!" - Επιλογή Θέματος - Επιλογή Γλώσσας + Επιλογή θέματος + Επιλογή γλώσσας "Μπλοκάρισμα http" "Άρνηση σύνδεσης στη θύρα http 80" "Ενημέρωση" - Αυτόματες Ενημερώσεις - Ελέγχετε καθημερινά τις ενημερώσεις του InviZible Pro και των πρόσθετων μονάδων - Ελεγχος Ενημέρωσης - "Ελέγξτε εάν είναι διαθέσιμες νέες εκδόσεις τώρα" + Αυτόματες ενημερώσεις + Ελέγχετε καθημερινά τις ενημερώσεις του InviZible Pro και των προσθέτων + Ελεγχος ενημέρωσης + Ελέγξτε εάν υπάρχουν νέες διαθέσιμες εκδόσεις τώρα "Ενημέρωση αυστηρά μέσω Tor" - "Ελέγξτε τις ενημερώσεις μόνο με το Tor και όταν το Tor εκτελείται" + Ελέγξτε για ενημερώσεις μόνο με το Tor και όταν εκτελείται το Tor "Χρήση Δικαιωμάτων Root" "Εκτέλεση Μονάδων ως Root" - "Χρησιμοποιήστε δικαιώματα Root για τις μονάδες DNSCrypt, Tor και I2P. Η ενεργοποίηση αυτής της δυνατότητας αφήνει τις μονάδες χωρίς διαχείριση και μπορεί να προκαλέσει προβλήματα σύνδεσης!" - "Λοιπά" + Χρησιμοποιήστε δικαιώματα Root για τα πρόσθετα DNSCrypt, Tor και I2P. Η ενεργοποίηση αυτής της δυνατότητας αφήνει τις μονάδες χωρίς διαχείριση και μπορεί να προκαλέσει προβλήματα σύνδεσης! + Άλλα "Αποκλεισμός HOTSPOT http" "Άρνηση σύνδεσης στη θύρα http 80 για HOTSPOT" - Προστατέψτε την εφαρμογή με τη λειτουργία No Root για να αποτρέψετε τον ξαφνικό τερματισμό της εφαρμογής από το σύστημα android + Προστατέψτε την εφαρμογή από τον ξαφνικό τερματισμό της από το android "Ανανέωση κανόνων" "Ενημερώστε τους κανόνες σε κάθε αλλαγή συνδεσιμότητας" "Εμφάνιση ειδοποίησης" - "Αποτρέψτε τον ύπνο της συσκευής" - Πρόσθετη προστασία για τη λειτουργία No Root ώστε αποτρέψετε τον ξαφνικό τερματισμό της εφαρμογής από το σύστημα android. Μπορεί όμως να εξαντλήσει την μπαταρία + Αποτρέψτε την αναστολή λειτουργίας της συσκευής + Μην αφήνετε τη συσκευή να πέσει σε κατάσταση νάρκης. Μπορεί να είναι χρήσιμο όταν χρησιμοποιείτε το HOTSPOT. Εξαντλεί την μπαταρία! "Βοηθητικά μηνύματα" - Εμφάνιση πάντα βοηθητικών μηνυμάτων - Εξαιρέστε το InviZible Pro από τη βελτιστοποίηση μπαταρίας στο Android. Διαφορετικά, το android θα τερματίζει το DNSCrypt, το Tor ή το I2P ανά πάσα στιγμή. Σε ειδικά συστήματα, όπως το MIUI, απαιτούνται ορισμένα πρόσθετα βήματα. + Εμφάνιση όλων των μηνυμάτων που έχουν επισημανθεί ως μη επιτρεπόμενα προς εμφάνιση + Παρακαλούμε αποκλείστε το InviZible Pro από τη βελτιστοποίηση της μπαταρίας του Android για να αποτρέψετε το σύστημα να τερματίσει το DNSCrypt, το Tor ή το I2P ανά πάσα στιγμή. Ορισμένα ειδικά συστήματα, όπως το MIUI, ενδέχεται να απαιτούν πρόσθετα βήματα. "Επιλογή BusyBox" "HOTSPOT-Πειραματικό" - Ξεκινήστε τη πρόσδεση κατά την εκκίνηση - Να επιτρέπεται η πρόσδεση Tor + Έναρξη κοινής χρήσης κατά την εκκίνηση + Να επιτρέπεται η κοινή χρήση Tor "Ανακατεύθυνση όλων μέσω Tor" - "Ανακατεύθυνση όλης της εισερχόμενης κίνησης μέσω Tor" - Επιλέξτε Ιστοσελίδες - Λίστα ιστοτόπων για άνοιγμα με Tor για HOTSPOT - Εξαίρεση Ιστοσελίδων - Λίστα ιστοτόπων για άνοιγμα απευθείας για HOTSPOT - Να επιτρέπετται πρόσδεση I2P + Ανακατεύθυνση όλης της εισερχόμενης κυκλοφορίας μέσω Tor + Επιλέξτε σελίδες + Λίστα τοποθεσιών για να ανοίξετε με Tor για HOTSPOT + Εξαίρεση τοποθεσιών + Λίστα τοποθεσιών για άνοιγμα απευθείας για HOTSPOT + Να επιτρέπεται η κοινή χρήση I2P "Διόρθωση TTL" - Το TTL θα διορθωθεί σε 64 χρησιμοποιώντας τοπικό VPN. Δεν απαιτείται υποστήριξη πυρήνα. Μπορεί να χρησιμοποιηθεί μόνο σε \"λειτουργία root\" με απενεργοποιημένη την επιλογή \"Εκτέλεση Μονάδων ως Root\". Για να διορθώσετε το TTL, ξεκινήστε το DNSCrypt ή/και το Tor. - Ανοίξτε ρυθμίσεις της πρόσδεσης + Το TTL θα καθοριστεί σε 64 χρησιμοποιώντας ένα τοπικό VPN. Δεν απαιτείται υποστήριξη kernel. Για να διορθώσετε το TTL, παρακαλώ ξεκινήστε το DNSCrypt ή/και το Tor. + Άνοιγμα ρυθμίσεων tethering "Παρακαλώ επανεκκινήστε το DNSCrypt" "Παρακαλώ επανεκκινήστε το Tor" "Παρακαλώ επανεκκινήστε το I2P" - "Επιλογή Iptables" + Επιλογή iptables "Αποθήκευση εντολών Root σε αρχείο καταγραφής" "DNSCrypt, Tor, I2P είναι προστατευμένα. Μην κρύβετε." - "Εκτέλεση εντολών Root..." + Εκτέλεση εντολών Root… "Παρακαλώ περιμένετε…" "Οι ρυθμίσεις αποθηκεύτηκαν" - "ΚΑΘΟΛΙΚΕΣ ΡΥΘΜΙΣΕΙΣ" + Καθολικές Ρυθμίσεις "Μόνο για προχωρημένους χρήστες!" "Επιλέξτε τουλάχιστον έναν διακομιστή!" "Τοπική θύρα για αναμονή." - "Απαιτούνται διακομιστές (από στατικές + απομακρυσμένες πηγές) για την ικανοποίηση συγκεκριμένων ιδιοτήτων." + Απαιτούνται διακομιστές (από στατικές + απομακρυσμένες πηγές) για την ικανοποίηση συγκεκριμένων ιδιοτήτων "Χρησιμοποιήστε διακομιστές που εφαρμόζουν το πρωτόκολλο DNSCrypt." "Χρησιμοποιήστε διακομιστές που εφαρμόζουν το πρωτόκολλο DNS-over-HTTPS." - "Απαιτούνται διακομιστές που ορίζονται από απομακρυσμένες πηγές για να ικανοποιούν συγκεκριμένες ιδιότητες." + Απαίτηση διακομιστών που ορίζονται από απομακρυσμένες πηγές να πληρούν συγκεκριμένες ιδιότητες "Ο διακομιστής πρέπει να υποστηρίζει επεκτάσεις ασφαλείας DNS (DNSSEC)." "Ο διακομιστής δεν πρέπει να καταγράφει ερωτήματα χρήστη (δηλωτικά)." "Ο διακομιστής δεν πρέπει να επιβάλλει τη δική του μαύρη λίστα (για γονικό έλεγχο, αποκλεισμό διαφημίσεων…)." - "Να χρησιμοποιείτε πάντα πρωτόκολλο TCP για να συνδέεστε σε διακομιστές upstream." - "Αυτό μπορεί να είναι χρήσιμο εάν χρειάζεται να δρομολογήσετε τα πάντα μέσω του Tor. Διαφορετικά, αφήστε αυτό ανενεργό, καθώς δεν βελτιώνει την ασφάλεια περισσότερο (το dnscrypt-proxy θα κρυπτογραφεί διαρκώς τα πάντα ακόμη ακόμη και με πρωτόκολλο UDP) και μπορεί μόνο να αυξήσει την καθυστέρηση." - "Διακομιστής SOCKS." - "Ενεργοποίηση διακομιστή" - "Δρομολογήστε όλες τις συνδέσεις TCP σε έναν τοπικό κόμβο Tor. Το Tor δεν υποστηρίζει UDP, οπότε ορίστε και το force_tcp σε ενεργό." - "Θύρα διακομιστή" - "Λοιπές Ρυθμίσεις." - Επίλυση με Bootstrap. Αυτή είναι μια κανονική, μη κρυπτογραφημένη λύση επίλυσης DNS, η οποία θα χρησιμοποιηθεί για ερωτήματα μιας μόνο λήψης κατά την ανάκτηση της αρχικής λίστας επιλυτών και μόνο εάν η διαμόρφωση DNS του συστήματος δεν λειτουργεί. Δεν θα χρησιμοποιηθεί ποτέ εάν οι λίστες έχουν ήδη αποθηκευτεί προσωρινά. - "Μην αφήνετε ποτέ το dnscrypt-proxy να προσπαθήσει να χρησιμοποιήσει τις ρυθμίσεις DNS του συστήματος. Χρησιμοποιήστε άνευ όρων την εναλλακτική λύση επίλυσης.." + Να χρησιμοποιείται πάντα TCP για σύνδεση με διακομιστές upstream + Χρησιμοποιήστε TCP αντί για UDP για να συνδεθείτε σε διακομιστές DNSCrypt. Αυτή η επιλογή θα πρέπει να είναι ενεργοποιημένη αν χρησιμοποιείτε το DNSCrypt μέσω Tor. + Διακομιστής SOCKS + Εξερχόμενος διακομιστής μεσολάβησης + Δρομολόγηση όλων των συνδέσεων TCP σε έναν τοπικό Tor εισερχόμενο διακομιστή μεσολάβησης Socks5. Το Tor δεν υποστηρίζει το UDP, γι\' αυτό θέστε, επίσης, το Force TCP σε true. + Θύρα μεσολάβησης + Άλλες ρυθμίσεις + Πρόκειται για έναν κανονικό, μη κρυπτογραφημένο επιλύτη DNS, ο οποίος θα χρησιμοποιείται μόνο για εφάπαξ ερωτήματα κατά την ανάκτηση της αρχικής λίστας επιλυτών, και μόνο εάν η διαμόρφωση DNS του συστήματος δεν λειτουργεί. Δεν θα χρησιμοποιηθεί ποτέ εάν οι λίστες έχουν ήδη αποθηκευτεί στην προσωρινή μνήμη. + Μην αφήνετε το DNSCrypt να προσπαθήσει να χρησιμοποιήσει τις ρυθμίσεις DNS του συστήματος. Χρησιμοποιήστε άνευ όρων τους επιλύτες Bootstrap. "ΦΙΛΤΡΑ" "Κανόνες προώθησης" "Δρομολογήστε ερωτήματα για συγκεκριμένους τομείς σε ένα αποκλειστικό σύνολο διακομιστών." @@ -409,21 +409,21 @@ Should it be named instead "Βασικές Ρυθμίσεις" (originating from "Νέες προεπιλεγμένες επικαλύψεις Tor είναι διαθέσιμες. Θέλετε να τα ενημερώσετε;" "Ανίχνευση επίθεσης MITM" "Ανίχνευση πλαστογράφησης ARP" - Ανίχνευση πλαστογράφησης ARP και τύπο Man-in-the-Middle επιθέσεων DHCP σε δίκτυα Wi-Fi. + Ανίχνευση πλαστογράφησης ARP τύπου Man-in-the-Middle και ψεύτικων επιθέσεων DHCP σε δίκτυα Wi-Fi. "Αποκλεισμός του διαδικτύου όταν εντοπιστεί επίθεση" - "Η σύνδεση στο Διαδίκτυο θα αποκλειστεί κατά τη διάρκεια της επίθεσης" + Η σύνδεση στο διαδίκτυο θα αποκλειστεί κατά τη διάρκεια της επίθεσης "Μεσολαβητής" "Χρήση μεσολαβητή socks5" "Το InviZible Pro θα πραγματοποιήσει όλες τις συνδέσεις μέσω του διακομιστή μεσολάβησης SOCKS5" "Λειτουργία συμβατότητας" - Ενεργοποιήστε το εάν η συσκευή σας είναι με προσαρμοσμένη ROM και η σύνδεση χάνεται όταν πατήσετε το κουμπί START + Ενεργοποιήστε το εάν η συσκευή σας είναι με προσαρμοσμένη rom και η σύνδεση χάνεται όταν πατήσετε το κουμπί START "Ενεργοποίηση ελέγχου κώδικα" - "Χρησιμοποιήστε την ακόλουθη εντολή για να διαχειριστείτε τις μονάδες της εφαρμογής: \"am broadcast -a pan.alexander.tordnscrypt.SHELL_SCRIPT_CONTROL --ei dnscrypt 1 --ei tor 1 --ei i2p 1 %s\". Όπου 1 - ξεκινά, 0 - σταματά τη μονάδα." + Χρησιμοποιήστε την ακόλουθη εντολή για να διαχειριστείτε τα πρόσθετα της εφαρμογής: \"am broadcast -a pan.alexander.tordnscrypt.SHELL_SCRIPT_CONTROL --ei dnscrypt 1 --ei tor 1 --ei i2p 1 %s\". Όπου 1 - ξεκινά, 0 - σταματά το πρόσθετο. "Διεύθυνση IP συσκευής LAN" - "Ενεργοποιήστε τη διόρθωση TTL και διαμορφώστε τη συσκευή LAN για σύνδεση στο InviZible. Χρησιμοποιήστε την προεπιλεγμένη πύλη: %1$s, και διακομιστή DNS: %2$s." + Ενεργοποιήστε τη διόρθωση TTL και διαμορφώστε τη συσκευή LAN για σύνδεση στο InviZible. Χρησιμοποιήστε την προεπιλεγμένη πύλη: %1$s, διακομιστή DNS: %2$s. "Υποστήριξη πολλών χρηστών" - Υποστήριξη για εφαρμογές Dual Apps, MIUI, Island, Shelter και προφίλ Work + Υποστήριξη για εφαρμογές Dual Apps, MIUI, Island, Shelter και προφίλ Work. Μπορεί να είναι αναποτελεσματική στη λειτουργία VPN "Ενεργοποίηση διακομιστή μεσολάβησης εξόδου SOCKS" "Το Tor θα κάνει όλες τις συνδέσεις OR μέσω του διακομιστή μεσολάβησης SOCKS 5." "Ενεργές υπηρεσίες" @@ -450,7 +450,7 @@ Should it be named instead "Βασικές Ρυθμίσεις" (originating from "Εντοπίστηκε ύποπτο DHCP!" "Εντοπίστηκε επίθεση Man-in-the-Middle! Τα δεδομένα σας μπορούν να υποκλαπούν από άλλη συσκευή στο τοπικό δίκτυο. Απενεργοποιήστε το Wi-Fi, περιμένετε μερικά δευτερόλεπτα και ενεργοποιήστε το Wi-Fi. Η χρήση του τρέχοντος σημείου πρόσβασης Wi-Fi ενδέχεται να μην είναι ασφαλής!" "Ο εντοπισμός πλαστογράφησης ARP δεν υποστηρίζεται για τη συσκευή σας!" - "Απενεργοποιήστε τους Κόμβους Εισόδου στις Ρυθμίσεις Tor, διαφορετικά δεν μπορείτε να χρησιμοποιήσετε τις Επικαλύψεις." + Παρακαλώ απενεργοποιήστε τους Κόμβους Εισόδου στις Ρυθμίσεις Tor, διαφορετικά δεν μπορείτε να χρησιμοποιήσετε τις Γέφυρες. "Παράκαμψη διευθύνσεων LAN" "Μην χρησιμοποιείτε το Tor για προορισμούς LAN και δεσμευμένα μπλοκ IP του IANA" "Απενεργοποιήστε τις επικαλύψεις Tor εάν θέλετε να επιλέξετε κόμβους εισόδου." @@ -470,7 +470,7 @@ Should it be named instead "Βασικές Ρυθμίσεις" (originating from "Δεν επιτρέπεται" "Λειτουργία Ημέρας" "Λειτουργία Νύχτας" - "Αυτόματα" + Αυτόματο "Προεπιλογή συστήματος" "Επιτρέψτε τις συνδέσεις σε τοπικό δίκτυο, ιστότοπους onion και i2p" "Επιτρέπονται οι συνδέσεις σε δίκτυα WiFi" @@ -487,8 +487,8 @@ Should it be named instead "Βασικές Ρυθμίσεις" (originating from "Απενεργοποιήστε τον διακομιστή μεσολάβησης στις ρυθμίσεις δικτύου WiFi Android. Αυτή η επιλογή παρεμβαίνει στον τρόπο λειτουργίας του InviZible." "Απενεργοποιήστε τον διακομιστή μεσολάβησης στις ρυθμίσεις APN δικτύου Android. Αυτή η επιλογή παρεμβαίνει στο InviZible." "Βοηθητικές ειδοποιήσεις" - "Προστασία επανασύνδεσης DNS" - "Αποκλεισμός ιστότοπου όταν εντοπίζεται επίθεση επανασύνδεσης DNS" + Προστασία επαναδέσμευσης DNS + Αποκλεισμός ιστότοπου όταν εντοπίζεται επίθεση επαναδέσμευσης DNS "Επανασύνδεση DNS" "Εντοπίστηκε πιθανή επίθεση επανασύνδεσης DNS! Ο ιστότοπος %s έχει αποκλειστεί." "Είσαι σίγουρος; Αυτή η ενέργεια δε μπορεί να αναιρεθεί!" @@ -497,31 +497,43 @@ Should it be named instead "Βασικές Ρυθμίσεις" (originating from "Εγινε επαναφορά αντιγράφου ασφαλείας" "Το InviZible Pro Project και ο προγραμματιστής του εκφράζουν την εκτίμηση τους για τη βοήθεια σας!" "Προσπαθήστε να χρησιμοποιήσετε την ενσωματωμένη επιτάχυνση υλικού κρυπτογράφησης όταν είναι διαθέσιμη." - "Σύνδεση..." - "Αναμονή για το δίκτυο..." + Σύνδεση… + Αναμονή για το δίκτυο… "Αλλαγή Tor IP" "Tor IP άλλαξε" "Έχετε προσθέσει περισσότερα από 3 εικονίδια στις Γρήγορες ρυθμίσεις. Αφήστε μόνο 3. Η προσθήκη περισσότερων από 3 εικονιδίων μπορεί να προκαλέσει προβλήματα με τον έλεγχο της εφαρμογής." - "Ανίχνευση ψεύτικων DHCP" - Εντοπίστε ψεύτικες DHCP για επιθέσεις τύπου Man-in-the-middle σε δίκτυα Wi-Fi. + Ανίχνευση ψεύτικου DHCP + Εντοπίστε ψεύτικες DHCP επιθέσεις τύπου Man-in-the-Middle σε δίκτυα Wi-Fi. "Επιλέξτε τύπο snowflake" "Αποκρύπτει το όνομα τομέα του από τον πάροχο Διαδικτύου. Το όνομα χρησιμοποιείται για τη δημιουργία μιας σύνδεσης." "Εμφάνιση όλων των εφαρμογών" "Οι εφαρμογές εμφανίζονται ανεξάρτητα από το αν η εφαρμογή μπορεί να συνδεθεί στο Διαδίκτυο ή όχι." "Εμφανίζονται μόνο οι εφαρμογές που μπορούν να συνδεθούν στο Διαδίκτυο." "Kill switch" - "Block internet connection when Tor, DNSCrypt and Purple I2P are stopped" - "Internet blocked due to the Kill switch. Start Tor, DNSCrypt or I2P to allow the connection. Or disable the Kill switch in Common Settings." + Αποκλεισμός σύνδεσης στο διαδίκτυο όταν σταματούν τα Tor, DNSCrypt και Purple I2P + Το Internet μπλοκαρίστηκε λόγω του διακόπτη Kill switch. Ξεκινήστε το Tor, το DNSCrypt ή το I2P για να επιτρέψετε τη σύνδεση. Ή απενεργοποιήστε τον διακόπτη Kill switch στις Κοινές ρυθμίσεις. "List is Empty" - "Auto" - "Use device BusyBox" - "Use application BusyBox" - "Not use BusyBox" - "Use application iptables" - "Use device iptables" - "Wait for the xtables lock" - "Wait until the exclusive iptables lock can be obtained to prevent concurrent modification of iptables rules." + Αυτόματο + Χρήση BusyBox συσκευής + Χρήση BusyBox εφαρμογής + Μην χρησιμοποιείς BusyBox + Χρήση iptables εφαρμογής + Χρήση iptables συσκευής + Περιμένετε το κλείδωμα των xtables + Περιμένετε μέχρι να αποκτήσετε το αποκλειστικό κλείδωμα iptables για να αποτρέψετε την ταυτόχρονη τροποποίηση των κανόνων iptables. "Internet connectivity check" - "Real-time logs" - "Show application connection logs in the DNS tab" + Αρχεία καταγραφής πραγματικού χρόνου + Εμφάνιση αρχείων καταγραφής σύνδεσης εφαρμογών στην καρτέλα DNS + Ρώτα αργότερα + Απενεργοποίηση της εξοικονόμησης δεδομένων δικτύου κινητής; + Χρησιμοποιήστε διακομιστές που εφαρμόζουν το πρωτόκολλο Oblivious DNS-over-HTTPS. + Εξαίρεση από Tor + Δρομολόγηση προς Tor + Εξαίρεσε UDP από Tor + Εξαίρεσε εντελώς + IPv6 γέφυρες + Σταματήστε την βελτιστοποίηση της χρήσης της μπαταρίας; + Ενεργοποιήστε αυτήν την επιλογή εάν βρίσκεστε σε ένα δίκτυο μόνο IPv6 και οι τοποθεσίες IPv4 δεν είναι διαθέσιμες. Μην την ενεργοποιήσετε διαφορετικά, διαφορετικά δεν θα μπορείτε να συνδεθείτε σε τίποτα απολύτως. + Παρακαλώ επιτρέψτε τη χρήση δεδομένων στο παρασκήνιο και επιτρέψτε τη χρήση δεδομένων ενώ η εξοικονόμηση δεδομένων είναι ενεργοποιημένη. Αυτό είναι απαραίτητο για μια ομαλή διαδικτυακή εμπειρία. + Ενεργοποιήστε την υποστήριξη για HTTP/3 (DoH3, HTTP over QUIC). Σημειώστε ότι, όπως και το DNSCrypt, αλλά σε αντίθεση με άλλες εκδόσεις HTTP, αυτό χρησιμοποιεί UDP και (συνήθως) τη θύρα 443 αντί για TCP. diff --git a/tordnscrypt/src/main/res/values-es/strings.xml b/tordnscrypt/src/main/res/values-es/strings.xml index 62766e8dc..1be6d8179 100644 --- a/tordnscrypt/src/main/res/values-es/strings.xml +++ b/tordnscrypt/src/main/res/values-es/strings.xml @@ -544,4 +544,14 @@ Dirección del proxy externo (IP o local). Las solicitudes fuera de I2P irán allí. Para activar esta opción, debe desactivar los puentes o utilizar puentes solo con puertos 80 y 443. No compartas circuitos con streams de diferentes aplicaciones. + Añadir lista local + Añadir regla + reglas + Total: %d reglas + Añadir URL + Añadir lista remota + Las normas se actualizarán tras el plazo especificado en horas. + Reemplazar lista remota + Reemplazar lista local + Habilite VPN siempre activa y bloquee conexiones sin VPN para que Invisible Pro bloquee Internet cuando la aplicación no se esté ejecutando diff --git a/tordnscrypt/src/main/res/values-fa/strings.xml b/tordnscrypt/src/main/res/values-fa/strings.xml index 1b94ec84a..c260f8bce 100644 --- a/tordnscrypt/src/main/res/values-fa/strings.xml +++ b/tordnscrypt/src/main/res/values-fa/strings.xml @@ -16,7 +16,7 @@ بررسی روت بودن دستگاه… لطفاً صبر کنید… - "مجدداً نشان داده نشود" + نشان نده "DNSCrypt متوقف شد" "DNSCrypt نصب شد" @@ -71,7 +71,7 @@ شروع تور هنگام راه‌اندازی مجدد - "شروع خودکار I2P" + شروع خودکار I2P "تأخیر" شروع با تأخیر (چند ثانیه) تنها اگر شروع خودکار به درستی کار نکرد "سرورهای DNSCrypt" @@ -523,7 +523,7 @@ استفاده از برنامه BusyBox از BusyBox استفاده نکن. پل IPv6 - مسدود کردن اینترنت زمانی که Tor, DNSCrypt و Purple I2P متوقف هستند. + مسدود کردن دسترسی به اینترنت زمانی Tor, DNSCrypt و Purple I2P متوقف هستند گزارش لحظه‌ای مستثنی کردن از Tor مسیر به Tor diff --git a/tordnscrypt/src/main/res/values-nb-rNO/strings.xml b/tordnscrypt/src/main/res/values-nb-rNO/strings.xml index 4f3f70ea9..a374b92f9 100644 --- a/tordnscrypt/src/main/res/values-nb-rNO/strings.xml +++ b/tordnscrypt/src/main/res/values-nb-rNO/strings.xml @@ -106,7 +106,7 @@ Bruk SOCKS5-mellomtjener Alle tilkoblinger vil bli gjort gjennom SOCKS5-mellomtjeneren Vis merknad - Kompabilitetsmodus + Kompatibilitetsmodus Hjelpemeldinger Alltid vis hjelpemeldinger Velg BusyBox diff --git a/tordnscrypt/src/main/res/values-pt-rBR/strings.xml b/tordnscrypt/src/main/res/values-pt-rBR/strings.xml index 006630261..f0da45eb8 100644 --- a/tordnscrypt/src/main/res/values-pt-rBR/strings.xml +++ b/tordnscrypt/src/main/res/values-pt-rBR/strings.xml @@ -13,7 +13,7 @@ "Iniciar durante o Boot" "Checando se o Root está disponível…" "Por favor, aguarde…" - "Não mostrar novamente" + Não mostrar "DNSCrypt Parado" "DNSCrypt instalado" "Instalando DNSCrypt" @@ -60,9 +60,9 @@ "Escolha uma pasta de backup:" "Configurações" "Iniciar automaticamente" - Iniciar o DNSCrypt durante o boot - Iniciar o Tor durante o boot - Iniciar o I2p durante o boot + Iniciar o DNSCrypt automaticamente + Iniciar o Tor automaticamente + Iniciar o I2p automaticamente "Atraso" Utilize o delay (em segundos) somente se a inicialização automática não funcionar corretamente "Servidores DNSCrypt" @@ -72,16 +72,16 @@ "Rotear todo o tráfego através do Tor" "Rotear todo o tráfego através do InviZible" Selecionar websites - Lista de websites para abrir com o Tor + Lista de sites para abrir com Tor. Este recurso não funciona para sites por trás de uma CDN Selecionar aplicativos "Lista de aplicativos para utilizar com o Tor" "Lista de aplicativos para usar com o InviZible" Websites ignorados - Lista de websites para abrir diretamente + Lista de sites para abrir diretamente. Este recurso não funciona para sites por trás de uma CDN Ignorar aplicativos "Lista de aplicativos para abrir diretamente" Intervalo de atualização - Período, em horas, para atualizar os IPs dos websites; Para o Android 5.1 e superior. Coloque 0 para parar a atualização. + Período, em horas, para atualizar os IPs dos websites. Para o Android 5.1 e superior. Coloque 0 para parar a atualização. "Pontes" "Use isso se você não conseguir se conectar a rede Tor" "Não usar Pontes" @@ -116,15 +116,15 @@ "Outro" "Bloquear HOTSPOT hhtp" "Negar conexão para o http na porta 80 para o HOTSPOT" - Proteger o aplicativo no modo sem Root para prevenir que o Android mate o processo + Proteger o aplicativo de ser terminado pelo Android "Atualizar regras" "Atualizar regras toda vez que a conexão mudar" "Mostrar notificação" "Impedir que o dispositivo durma" Proteção adicional no modo sem Root para prevenir que o aplicativo seja morto pelo Android. Pode drenar bateria "Mensagens de ajuda" - Sempre mostrar mensagens de ajuda - Por favor, exclua o InviZible Pro da otimização de bateria do Android. Caso contrário, o android parará o DNSCrypt, Tor ou I2P em qualquer momento. Em sistemas especiais, como o MIUI, são necessários alguns passos adicionais. + Mostrar todas as mensagens que foram marcadas como não permitidas para exibição + Por favor, exclua o InviZible Pro da otimização da bateria do Android para evitar que o sistema termine o DNSCrypt, Tor ou I2P a qualquer momento. Alguns sistemas especiais, como MIUI, podem exigir passos adicionais. "Selecionar BusyBox" "HOTSPOT-Experimental" @@ -133,7 +133,7 @@ Permir Tor Tethering "Rotear tudo através do Tor" "Rotear todo tráfego de entrada através do Tor" - Selecionar websites + Selecionar sites Lista de websites para abrir com o Tor pelo HOTSPOT Excluir websites @@ -164,17 +164,17 @@ "O servidor não deve manter log das queries do usuário (declarativo)." O servidor não deve forçar sua própria lista negra (para controle parental, bloqueio de propaganda…). - "Sempre usar TCP para conectar com os servidores upstream." + Sempre use TCP para se conectar a servidores upstream Isso pode ser útil se você precisar rotear tudo através do Tor.. Caso contrário, deixe como falso, pois isso não melhora a segurança (dnscrypt-proxy sempre criptografará tudo, mesmo usando UDP) e só aumentará a latência. - SOCKS Proxy. + Proxy SOCKS "Ativar proxy" "Roteie todas as conexões TCP para um nó Tor local. Tor não suporta UDP, então defina force_tcp como true também." - "Porta de Proxy" - Outras configurações. - Resolvedor substituto. Este é um resolvedor DNS normal, não criptografado, que só será usado para consultas únicas ao recuperar a lista inicial de resolvedores e somente se a configuração DNS do sistema não funcionar. Nunca será usado se as listas já tiverem sido armazenadas em cache. + Porta de Proxy + Outras Configurações + Esse é um resolvedor de DNS normal, não criptografado, que será usado somente para consultas únicas ao recuperar a lista inicial de resolvedores e somente se a configuração de DNS do sistema não funcionar. Ele nunca será usado se as listas já tiverem sido armazenadas em cache. "Nunca deixe dnscrypt-proxy tentar usar as configurações de DNS do sistema. Use incondicionalmente o resolvedor de fallback." - "FILTROS" + Filtros Regras de encaminhamento "Roteie as consultas de domínios específicos para um conjunto dedicado de servidores." @@ -193,26 +193,26 @@ "Ativar log de consultas suspeitas" "Abrir log de consultas suspeitas" - "Bloqueio baseado em padrões (lista negra)." + Bloqueio baseado em padrões (lista negra) "Lista negra" "A lista negra é composta de uma regra a cada linha." - "Bloqueio de IP baseado em padrões (lista negra de IP)." + Bloqueio de IP baseado em padrões (lista negra de IP) "Lista negra de IPs" "A lista negra de IP é composta de uma regra por linha." - "Lista de permissões baseada em padrões (ignorar listas negras)." + Lista de permissões baseada em padrões (ignorar listas negras) Lista branca "A lista branca suporta os mesmos padrões das listas negras. Se um nome corresponder a uma entrada da lista de permissões, a sessão correspondente irá ignorar nomes e filtros de IP." "Servidores" "Fontes" "Listas remotas de servidores disponíveis." - "Lista de fontes expirando após horas de refresh_delay." + A lista de servidores será atualizada após o atraso especificado em horas. "Retransmissores" "Fontes" - "Lista de Retransmissores expirarár depois de refresh_delay horas." + A lista de relés será atualizada após o atraso especificado em horas. "DNSCrypt Retransmissores" "Não existe preferência em dnscrypt-proxy.toml!" "Apagar log" @@ -234,11 +234,11 @@ "A cada X segundos, considere se deve construir um novo circuito." "Sinta-se à vontade para reutilizar um circuito que foi usado pela primeira vez no máximo NUM segundos atrás, mas nunca conecte um novo fluxo a um circuito que seja muito antigo." "Se habilitado, o Tor não colocará dois servidores cujos endereços IP estão muito próximos no mesmo circuito. Atualmente, dois endereços estão muito próximos se estiverem no mesmo intervalo / 16." - "Ativar SOCKS proxy" + Proxy SOCKS Abra esta porta para ouvir conexões de aplicativos de fala SOCKS. - "Ativar HTTPTunnel" + Túnel HTTP "Abra esta porta para ouvir conexões de aplicativos de fala SOCKS." - "Ativar proxy transparente" + Proxy transparente Abra esta porta para ouvir conexões de proxy transparentes. "Ativar DNS" "Abra esta porta para escutar as solicitações UDP DNS e resolvê-las anonimamente." @@ -297,7 +297,7 @@ "Parece que o DNSCrypt foi morto pelo sistema Android. Sua conexão com a Internet foi restaurada. Verifique as configurações do dispositivo!" "Parece que o Tor foi morto pelo sistema Android. Sua conexão com a Internet foi restaurada. Verifique as configurações do dispositivo!" "Parece que o I2P foi morto pelo sistema Android. Sua conexão com a Internet foi restaurada. Verifique as configurações do dispositivo!" - "Parece que o DNSCrypt NÃO PODE se conectar à INTERNET." + Parece que o DNSCrypt não consegue se conectar à Internet. Você pode tentar escolher outros servidores DNSCrypt. Encontre-os em MENU -> Configurações Rápidas -> Selecionar servidores DNSCrypt Parece que o Tor não consegue se conectar à Internet. O ISP pode bloquear as conexões Tor. Você pode tentar usar as Tor Bridges. Encontre-as em MENU -> Configurações rápidas -> Bridges "Bloqueio para crianças" "Você pode bloquear o controle deste aplicativo. Por favor, digite a senha ou use a anterior." @@ -323,7 +323,7 @@ "A atualização do DNSCrypt Pro está disponível. Quer fazer o download e atualizá-lo? A atualização continuará em segundo plano." "A atualização do Tor está disponível. Quer fazer o download e atualizá-lo? A atualização continuará em segundo plano." "A atualização do I2P está disponível. Quer fazer o download e atualizá-lo? A atualização continuará em segundo plano." - Verificar Atualizações + Verificando atualizações Aguarde enquanto as atualizações estão sendo verificadas. "O servidor de atualização está temporariamente indisponível. Por favor, tente novamente mais tarde." "O servidor de atualização não estava disponível." @@ -338,7 +338,7 @@ "Parece que esta versão não é oficial do InviZible. Por favor, cuide disso!" "Apenas para a versão PRO" "Doação" - "O Projeto InviZible Pro precisa de sua ajuda. Visite a página de doações ou insira o código de registro recebido." + O projeto InviZible Pro busca sua ajuda. Visite a página de doações ou digite o código premium que você já recebeu. "Visite" "Coloque o código" "Coloque o código" @@ -354,7 +354,7 @@ "Adicionar servidor customizado" "Configuração de servidor personalizado inválida. Verifique o campo SDNS." "Observe que você tem uma compra pendente:" - "O Projeto InviZible Pro precisa de sua ajuda. Pressione OK para adquirir recursos premium." + O projeto InviZible Pro quer sua ajuda. Pressione OK para comprar recursos premium. "Desculpe, mas é impossível confirmar sua compra. Você receberá um reembolso após 3 dias." "Infelizmente, esse recurso está disponível apenas para a versão premium." Outproxy HTTP @@ -510,9 +510,9 @@ "Use device iptables" "Wait for the xtables lock" "Wait until the exclusive iptables lock can be obtained to prevent concurrent modification of iptables rules." - "Internet connectivity check" - "Real-time logs" - "Show application connection logs in the DNS tab" + Verificação de conectividade com a Internet + Registros em tempo real + Mostrar registros de conexão de aplicativos na guia DNS \tObrigado porescolher o InviZible Pro. Espero que seja útil para a sua privacidade e conforto no uso da Internet. \n\n\tInviZible Pro inclui Tor, DNSCrypt e Purple I2P como módulos. @@ -540,12 +540,12 @@ "Desativar o economizador de dados da rede?" "Por favor, permita o uso de dados em segundo plano e o uso de dados enquanto o economizador de dados está ativado. Isso é essencial para uma experiência online suave." "Executando comandos de Root…" - "O Tor entra em um estado de inatividade se não detectar atividade do cliente por um tempo especificado. Deve ser de pelo menos 10 minutos." + O Tor entra em um estado de inatividade se não detectar atividade do cliente por um tempo especificado. Deve ser de pelo menos 10 minutos "Pressione para adicionar." "Pressione e segure para adicionar." "Pressione para editar." "Pressione e segure para editar." - "Anonimizar retransmissores." + Anonimizar retransmissores "Os retransmissores anonimizados não estão em uso." "Excluir do Tor" "Roteirizar para o Tor" @@ -561,4 +561,11 @@ "Endereço do outproxy (IP ou local). As solicitações fora do I2P serão enviadas para lá." "Para ativar esta opção, você deve desativar as pontes ou usar pontes com as portas 80 e 443 apenas." "Não compartilhe circuitos com conexões de diferentes aplicativos." + Adicionar lista remota + Adicionar lista local + Adicionar regra + regras + Total: %d regras + Adicionar URL + As regras serão atualizadas após o atraso especificado em horas. diff --git a/tordnscrypt/src/main/res/values-ru/strings.xml b/tordnscrypt/src/main/res/values-ru/strings.xml index 35e5d119e..1d55dac33 100644 --- a/tordnscrypt/src/main/res/values-ru/strings.xml +++ b/tordnscrypt/src/main/res/values-ru/strings.xml @@ -182,6 +182,7 @@ Источники Список серверов. Список серверов будет обновлен после указанной задержки в часах. + Правила будут обновлены после указанной задержки в часах. Настройка не существует в dnscrypt-proxy.toml! Удалить лог Лог запросов DNS @@ -515,6 +516,7 @@ Показаны только те приложения, которые могут подключаться к Интернету. Аварийный блокировщик Блокировать подключение к Интернету, когда Tor, DNSCrypt и Purple I2P остановлены + Включите Постоянный VPN и Блокировать соединения без VPN для InviZible Pro, чтобы заблокировать интернет, когда приложение не запущено Интернет заблокирован Аварийным блокировщиком. Запустите Tor, DNSCrypt или Purple I2P, чтобы разрешить соединение. Либо выключите Аварийный блокировщик в Общих настройках. Список пустой Правила переадресации @@ -543,4 +545,12 @@ Исключить полностью \u2193 Потяните, чтобы обновить \u2193 Блокировка этого системного приложения может привести к нестабильному интернет-соединению! + Добавить ссылку + Заменить ссылку + Добавить файлы + Заменить файлы + Добавить правило + правил + Всего: %d правил + Ссылка diff --git a/tordnscrypt/src/main/res/values-uk/strings.xml b/tordnscrypt/src/main/res/values-uk/strings.xml index bbb3f6579..cab636b46 100644 --- a/tordnscrypt/src/main/res/values-uk/strings.xml +++ b/tordnscrypt/src/main/res/values-uk/strings.xml @@ -513,4 +513,14 @@ Ноди Налаштування ізолювання Щоб активувати цю опцію, необхідно відключити мости, або використовувати мости тільки з 80 і 443 портами. + Добавити посилання + Замінити посилання + Добавити файли + Замінити файли + Добавити правило + правил + Всього: %d правил + Посилання + Правила будуть оновлені після зазначеної затримки у годинах. + Увімкніть Постійний VPN і Блокувати з\'єднання без VPN для InviZible Pro, щоб заблокувати інтернет, коли додаток не запущений diff --git a/tordnscrypt/src/main/res/values-vi/strings.xml b/tordnscrypt/src/main/res/values-vi/strings.xml new file mode 100644 index 000000000..a6b3daec9 --- /dev/null +++ b/tordnscrypt/src/main/res/values-vi/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/tordnscrypt/src/main/res/values-zh/strings.xml b/tordnscrypt/src/main/res/values-zh/strings.xml index 2f833514b..086dd2cdc 100644 --- a/tordnscrypt/src/main/res/values-zh/strings.xml +++ b/tordnscrypt/src/main/res/values-zh/strings.xml @@ -546,4 +546,13 @@ 隔离设置 要激活此选项,应禁用网桥或仅使用具有80和443端口的网桥。 不要与来自不同应用程序的流共享链路。 + 添加远程列表 + 添加本地列表 + 规则 + 添加URL + 规则将在指定的延迟时间后更新。 + 添加规则 + 总计:%d条规则 + 替换远程列表 + 替换本地列表 diff --git a/tordnscrypt/src/main/res/values/array.xml b/tordnscrypt/src/main/res/values/array.xml index d93098c41..608b599bc 100644 --- a/tordnscrypt/src/main/res/values/array.xml +++ b/tordnscrypt/src/main/res/values/array.xml @@ -493,7 +493,6 @@ @string/pref_tor_snowflake_amp - @string/pref_tor_snowflake_fastly @string/pref_tor_snowflake_cdn77 @string/pref_tor_snowflake_azure @string/pref_tor_snowflake_amazon @@ -503,7 +502,6 @@ 2 3 4 - 5 @@ -592,4 +590,10 @@ verizon.com eset.com + + http://stats.i2p/cgi-bin/newhosts.txt + http://i2p-projekt.i2p/hosts.txt + http://notbob.i2p/hosts-all.txt + http://rus.i2p/hosts.txt + diff --git a/tordnscrypt/src/main/res/values/colors.xml b/tordnscrypt/src/main/res/values/colors.xml index 4e52c4590..66beecfde 100644 --- a/tordnscrypt/src/main/res/values/colors.xml +++ b/tordnscrypt/src/main/res/values/colors.xml @@ -5,6 +5,7 @@ #0D47A1 @color/colorWhite @color/colorBlack + #808080 #803F64B5 #FFFFFF #000000 diff --git a/tordnscrypt/src/main/res/values/strings.xml b/tordnscrypt/src/main/res/values/strings.xml index e404249b8..4e2ee7813 100644 --- a/tordnscrypt/src/main/res/values/strings.xml +++ b/tordnscrypt/src/main/res/values/strings.xml @@ -1,3 +1,4 @@ + InviZible Pro Open navigation drawer @@ -6,12 +7,10 @@ best solution for your privacy Navigation header Settings - Start DNSCrypt Stop DNSCrypt Start Tor Stop Tor - Start I2P Stop I2P I2P Stopped @@ -22,40 +21,30 @@ I2P Installed I2P Not Installed Purple I2P version - Start on Boot - Device Root Busy Box Root Checking if Root is available… - Please wait… Don\'t show - - DNSCrypt Stopped DNSCrypt Installed DNSCrypt Installing DNSCrypt Not Installed - DNSCrypt Starting DNSCrypt Running DNSCrypt Stopping DNSCrypt-proxy version - - Tor Stopped Tor Installed Tor Installing Tor Not Installed - Tor Starting Connecting Tor Running Tor Stopping Tor version - OK Cancel Error! @@ -75,13 +64,10 @@ Deny Are you sure? This action cannot be undone! Ask later - - + You can use InviZible Pro with local VPN mode, or applications with own proxy or local VPN feature in proxy mode. You can use InviZible Pro with applications with own proxy or local VPN feature in proxy mode. - Save file Error! - Common Settings DNSCrypt Settings Tor Settings @@ -91,7 +77,6 @@ About Logs Donate - Restore Settings Save Settings Reset Settings @@ -99,13 +84,10 @@ Choose backup folder: Backup saved Backup restored - Root Mode Proxy Mode VPN Mode - Settings - Autostart Autostart DNSCrypt Autostart Tor @@ -173,7 +155,6 @@ Check if new versions are available now Update strictly through Tor Check updates only with Tor and when Tor is running - Use Root Privileges Run Modules With Root Use Root privileges for DNSCrypt, Tor and I2P modules. Enabling this feature leaves modules unmanaged and may cause connection problems! @@ -196,6 +177,7 @@ Kill switch Block internet connection when Tor, DNSCrypt and Purple I2P are stopped Refresh rules + Enable Always-on VPN and Block connections without VPN for InviZible Pro to block the internet when the app is not running Update rules on every connectivity change Show notification Prevent device sleep @@ -235,35 +217,24 @@ Please restart I2P Multi-user support Support for Dual Apps, MIUI, Island, Shelter and Work profile applications. May be ineffective in VPN mode - Select iptables Use application iptables Use device iptables Wait for the xtables lock Wait until the exclusive iptables lock can be obtained to prevent concurrent modification of iptables rules. - Save Root Commands to Log - - DNSCrypt, Tor, I2P are protected. Don\'t hide. Executing Root commands… Connecting… Waiting for network… - Internet blocked due to the Kill switch. Start Tor, DNSCrypt or I2P to allow the connection. Or disable the Kill switch in Common Settings. - Please wait… Settings saved - - Global settings - For advanced user only! - Select at least one server! Local port to listen to. Listen port - Require servers (from static + remote sources) to satisfy specific properties Use servers implementing the DNSCrypt protocol. DNSCrypt servers @@ -283,7 +254,6 @@ Non-Logging Keep Logs DNSSEC - Require servers defined by remote sources to satisfy specific properties Server must support DNS security extensions (DNSSEC). Require DNSSEC @@ -291,16 +261,13 @@ Require no log Server must not enforce its own blacklist (for parental control, ads blocking…). Require no filter - Always use TCP to connect to upstream servers Use TCP instead of UDP to connect to DNSCrypt servers. This option should be enabled if you are using DNSCrypt over Tor. Force TCP - SOCKS proxy Outbound proxy Route all TCP connections to a local Tor inbound Socks5 proxy. Tor doesn\'t support UDP, so set Force TCP to true as well. Proxy port - Other Settings This is a normal, non-encrypted DNS resolver, that will be only used for one-shot queries when retrieving the initial resolvers list, and only if the system DNS configuration doesn\'t work. It will never be used if lists have already been cached. Bootstrap resolvers @@ -312,82 +279,68 @@ Activate this option if you are on an IPv6-only network and IPv4 sites become unavailable. Don\'t enable it otherwise, or you won\'t be able to connect to anything at all. DNS64 prefix Set of used static IPv6 prefixes. - Filters - Forwarding rules Forwarding rules Route queries for specific domains to a dedicated set of servers. Import forwarding rules Import a forwarding rules file. You can select multiple files, duplicate lines will be removed. Erase forwarding rules - Cloaking rules Cloaking rules Cloaking returns a predefined address for a specific name. In addition to acting as a HOSTS file, it can also return the IP address of a different name. It will also do CNAME flattening. Import cloaking rules Import a cloaking rules file. You can select multiple files, duplicate lines will be removed. Erase cloaking rules - Query logging Log client queries to a file. Query logging Do not log these query types, to reduce verbosity. Keep empty to log everything. Ignored qtypes Open Query Log - Suspicious queries logging Log queries for nonexistent zones. These queries can reveal the presence of malware, broken/obsolete applications, and devices signaling their presence to 3rd parties. Suspicious logging Open Suspicious Log - Pattern-based blocking (blacklist) Blacklist Blacklist are made of one pattern per line. Import blacklist Import a domain list or hosts file. You can select multiple files, duplicate lines will be removed. Erase blacklist - - Pattern-based IP blocking (IP blacklist) IP Blacklist IP Blacklist are made of one pattern per line. Import IP blacklist Import IP blacklist. You can select multiple files, duplicate lines will be removed. Erase IP blacklist - Pattern-based whitelisting (blacklists bypass) Whitelist Whitelist support the same patterns as blacklists. If a name matches a whitelist entry, the corresponding session will bypass names and IP filters. Import whitelist Import a domain list. You can select multiple files, duplicate lines will be removed. Erase whitelist - Servers Sources Remote lists of available servers. Refresh delay Server list will be updated after the specified delay in hours. - + Rules will be updated after the specified delay in hours. Relays Sources Refresh delay Relay list will be updated after the specified delay in hours. DNSCrypt Relays - - Preference does not exist in dnscrypt-proxy.toml! - Delete log DNSCrypt Query Log DNSCrypt Suspicious Log Log is Empty - DNSCrypt Forwarding rules - DNSCrypt Cloaking rules - DNSCrypt Blacklist - DNSCrypt IP Blacklist - DNSCrypt Whitelist - + Forwarding rules + Cloaking rules + Blacklist + IP Blacklist + Whitelist Block unqualified Immediately respond to A and AAAA queries for host names without a domain name. Block undelegated @@ -395,18 +348,14 @@ Edit dnscrypt-proxy.toml directly Block IPv6 Immediately respond to IPv6-related queries with an empty response. This makes things faster when there is no IPv6 connectivity, but can also cause reliability issues with some stub resolvers. - Please turn off \"Private DNS\" in Android network settings. This option interferes with InviZible. Please disable proxy in Android WiFi network settings. This option interferes with InviZible. Please disable proxy in Android Mobile network APN settings. This option interferes with InviZible. - Import Rules Please wait… Imported %d rules. Done! Imported %d rules. Done! The rules are erased. Too many rules to display. Only the first 1000 is shown. - - Virtual Addr Network When Tor needs to assign a virtual (unused) address because of a MAPADDRESS command from the controller or the AutomapHostsOnResolve feature, Tor picks an unassigned address from this range. Hardware Accel @@ -466,7 +415,6 @@ Select rendezvous Hides the broker\'s domain name from the Internet provider. The broker is used to establish a connection. AMP - Fastly CDN77 AZURE AMAZON @@ -484,13 +432,10 @@ Don’t share circuits with streams targeting a different destination address. Isolate Dest Port Don’t share circuits with streams targeting a different destination port. - Apps to use with InviZible Apps to bypass InviZible - Select All Remove Selection - Common settings Incoming connections Incoming port @@ -563,14 +508,12 @@ Preference not exist in i2pd.conf! Edit i2pd.conf directly Edit tunnels.conf directly - Info Looks like DNSCrypt was killed by android system. Your internet connection was restored. Check device Settings! Looks like Tor was killed by android system. Your internet connection was restored. Check device Settings! Looks like I2P was killed by android system. Check device Settings! - Looks like DNSCrypt can\'t connect to the internet. You can try to choose another DNSCrypt servers. Please find them in MENU -> Fast Settings -> Select DNSCrypt servers - Looks like Tor can\'t connect to the internet. ISP may blocks Tor connections. You can try to use Tor Bridges. Please find them in MENU -> Fast Settings -> Bridges - + Looks like DNSCrypt can\'t connect to the internet. You can try to choose another DNSCrypt servers. Please find them in MENU -> Fast Settings -> Select DNSCrypt servers + Looks like Tor can\'t connect to the internet. ISP may blocks Tor connections. You can try to use Tor Bridges. Please find them in MENU -> Fast Settings -> Bridges Child Lock You can lock control of this application. Please enter password, or use previous. Please enter password. @@ -579,18 +522,15 @@ Control Locked HOTSPOT Root - Collect logs Path for logs: Logs Saved. Please send InvizibleLogs.txt to developer. You can find logs in the folder: - Update Some of InviZible modules are ready for update. Do you want to update its? This will overwrite your modules settings! Update Later Not ask again Please restart InviZible Pro and allow complete installation! After installation reconfigure HOTSPOT, if you use it! - Version: Free Premium @@ -626,7 +566,6 @@ Angads25/android-filepicker license Meefik/busybox license https://invizible.net - Downloading file Download was stopped CANCEL DOWNLOAD @@ -647,7 +586,6 @@ Looks like you have activated 3 copies of InviZible with this code already. If it is wrong, please contact the developer. Looks like your PRO code is wrong. Please contact the developer. You checked InviZible Pro updates more than 5 times a day. Please try again later. - Looks like this unofficial version of InviZible. Please be care of it! Only for PRO version Donate @@ -658,25 +596,20 @@ InviZible Pro Project and its author express appreciation for the assistance. Would you like to download and upgrade to PRO? The update will continue in the background. InviZible Pro Project and its author express appreciation for the assistance! Remote lists of available relays. - VPN mode is active VPN mode is off VPN mode error! Please configure the use of proxy server in the settings of the connected device. Default values: IP:10.1.10.1 Port:8118 - START STOP Hide IP with TOR Defend with DNSCRYPT Access to I2P network with Purple I2P - Attention Reset InviZible Pro cannot start %1$s! Please try resetting %2$s settings. If this does not help, please restart your device. - Add custom server Invalid custom server configuration. Please check the SDNS field. - Please note that you have a pending purchase: InviZible Pro Project seeks your assistance. Press OK to purchase premium features. Sorry, but it is impossible to confirm your purchase. You will receive a refund after 3 days. @@ -692,19 +625,15 @@ \n\tinvizible.soft@gmail.com \n\tinvizible.net/en/privacy - Do you want to save the changes? It can break InviZible Pro. - Running services Update notifications Root commands notification Firewall notifications Auxiliary notifications - A crash report has been discovered. Want to send it to the developer to make InviZible better? New Tor identity Tor identity has changed - Proxy server: 127.0.0.1 Proxy port: @@ -768,4 +697,12 @@ Long Press to edit. Anonymize relays Anonymize relays are not used. + Add remote list + Replace remote list + Add local list + Replace local list + Add rule + rules + Total: %d rules + Add URL diff --git a/tordnscrypt/src/main/res/xml/preferences_common.xml b/tordnscrypt/src/main/res/xml/preferences_common.xml index 6f4993967..150b9adb8 100644 --- a/tordnscrypt/src/main/res/xml/preferences_common.xml +++ b/tordnscrypt/src/main/res/xml/preferences_common.xml @@ -123,6 +123,11 @@ android:layout="@layout/preferences_category_custom" android:title="@string/pref_common_categ_other"> + - @@ -206,10 +202,6 @@ android:targetClass="pan.alexander.tordnscrypt.settings.SettingsActivity" android:targetPackage="@string/package_name" /> - @@ -227,10 +219,6 @@ android:targetClass="pan.alexander.tordnscrypt.settings.SettingsActivity" android:targetPackage="@string/package_name" /> - @@ -248,10 +236,6 @@ android:targetClass="pan.alexander.tordnscrypt.settings.SettingsActivity" android:targetPackage="@string/package_name" /> - @@ -269,14 +253,23 @@ android:targetClass="pan.alexander.tordnscrypt.settings.SettingsActivity" android:targetPackage="@string/package_name" /> - + + +