From dfb26a0d2c95d56f69f5e1e0c255d9d5d6788120 Mon Sep 17 00:00:00 2001 From: pacien Date: Sat, 29 Jul 2023 23:03:12 +0200 Subject: storage: remove embedded FTP server Moving back the configuration files and logs to the user-accessible storage. Everything should be accessible through a file manager using the "USB storage" mode. The embedded FTP server is no longer necessary. --- app/build.gradle | 12 -- app/proguard-rules.pro | 4 +- app/src/main/AndroidManifest.xml | 7 - .../configure/ConfigurationAccessServerFragment.kt | 77 -------- .../tincapp/context/AppNotificationManager.kt | 13 -- .../tincapp/service/ConfigurationAccessService.kt | 204 --------------------- app/src/main/res/layout/configure_activity.xml | 14 +- ...nfigure_tools_configuration_access_fragment.xml | 86 --------- app/src/main/res/values-ru-rRU/strings.xml | 10 - app/src/main/res/values/strings.xml | 10 - readme.md | 1 - 11 files changed, 3 insertions(+), 435 deletions(-) delete mode 100644 app/src/main/java/org/pacien/tincapp/activities/configure/ConfigurationAccessServerFragment.kt delete mode 100644 app/src/main/java/org/pacien/tincapp/service/ConfigurationAccessService.kt delete mode 100644 app/src/main/res/layout/configure_tools_configuration_access_fragment.xml diff --git a/app/build.gradle b/app/build.gradle index 5d2da3f..5d49965 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -71,17 +71,6 @@ android { dataBinding = true } - packagingOptions { - resources { - excludes += [ - 'META-INF/DEPENDENCIES', - 'META-INF/spring.schemas', - 'META-INF/spring.handlers', - 'META-INF/license.txt', - ] - } - } - namespace 'org.pacien.tincapp' } @@ -101,7 +90,6 @@ dependencies { implementation('org.apache.commons:commons-configuration2:2.3') { exclude group: 'commons-logging', module: 'commons-logging' } implementation('commons-beanutils:commons-beanutils:1.9.3') { exclude group: 'commons-logging', module: 'commons-logging' } implementation('commons-io:commons-io:2.6') { exclude group: 'commons-logging', module: 'commons-logging' } - implementation('org.apache.ftpserver:ftpserver:1.1.1') { exclude group: 'org.slf4j', module: 'slf4j-log4j12' } } repositories { diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index ffbb3e3..2bb4a71 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,5 +1,5 @@ # Tinc App, an Android binding and user interface for the tinc mesh VPN daemon -# Copyright (C) 2017-2020 Pacien TRAN-GIRARD +# Copyright (C) 2017-2023 Pacien TRAN-GIRARD # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -15,8 +15,6 @@ # along with this program. If not, see . -keep class org.apache.commons.** { *; } --keep class org.apache.mina.** { *; } --keep class org.apache.ftpserver.** { *; } -keep class org.bouncycastle.** -keep class ch.qos.** { *; } -keep class org.slf4j.** { *; } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8d71109..827f421 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,9 +26,6 @@ - - - @@ -91,10 +88,6 @@ - - - diff --git a/app/src/main/java/org/pacien/tincapp/activities/configure/ConfigurationAccessServerFragment.kt b/app/src/main/java/org/pacien/tincapp/activities/configure/ConfigurationAccessServerFragment.kt deleted file mode 100644 index c90299a..0000000 --- a/app/src/main/java/org/pacien/tincapp/activities/configure/ConfigurationAccessServerFragment.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Tinc App, an Android binding and user interface for the tinc mesh VPN daemon - * Copyright (C) 2017-2020 Pacien TRAN-GIRARD - * - * This program 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. - * - * This program 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 this program. If not, see . - */ - -package org.pacien.tincapp.activities.configure - -import android.content.Intent -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.databinding.Observable -import androidx.databinding.ObservableBoolean -import org.pacien.tincapp.activities.BaseFragment -import org.pacien.tincapp.databinding.ConfigureToolsConfigurationAccessFragmentBinding -import org.pacien.tincapp.service.ConfigurationAccessService - -/** - * @author pacien - */ -class ConfigurationAccessServerFragment : BaseFragment() { - private val ftpServerStartListener = object : Observable.OnPropertyChangedCallback() { - override fun onPropertyChanged(sender: Observable, propertyId: Int) { - binding.ftpEnabled = (sender as ObservableBoolean).get() - } - } - - private lateinit var binding: ConfigureToolsConfigurationAccessFragmentBinding - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - binding = ConfigureToolsConfigurationAccessFragmentBinding.inflate(inflater, container, false) - binding.toggleFtpState = { toggleServer() } - setConnectionInfo() - return binding.root - } - - override fun onResume() { - super.onResume() - setConnectionInfo() - ConfigurationAccessService.runningState.addOnPropertyChangedCallback(ftpServerStartListener) - binding.ftpEnabled = ConfigurationAccessService.runningState.get() - } - - override fun onPause() { - ConfigurationAccessService.runningState.removeOnPropertyChangedCallback(ftpServerStartListener) - super.onPause() - } - - private fun setConnectionInfo() { - binding.ftpUsername = ConfigurationAccessService.getFtpUsername() - binding.ftpPassword = ConfigurationAccessService.getFtpPassword() - binding.ftpPort = ConfigurationAccessService.getFtpPort() - } - - private fun toggleServer() { - val targetServiceIntent = Intent(requireContext(), ConfigurationAccessService::class.java) - - if (binding.ftpEnabled) - requireContext().stopService(targetServiceIntent) - else - requireContext().startService(targetServiceIntent) - } -} diff --git a/app/src/main/java/org/pacien/tincapp/context/AppNotificationManager.kt b/app/src/main/java/org/pacien/tincapp/context/AppNotificationManager.kt index 5b01a54..d6e21f5 100644 --- a/app/src/main/java/org/pacien/tincapp/context/AppNotificationManager.kt +++ b/app/src/main/java/org/pacien/tincapp/context/AppNotificationManager.kt @@ -36,10 +36,7 @@ import org.pacien.tincapp.utils.PendingIntentUtils class AppNotificationManager(private val context: Context) { companion object { private const val ERROR_CHANNEL_ID = "org.pacien.tincapp.notification.channels.error" - private const val CONFIG_ACCESS_CHANNEL_ID = "org.pacien.tincapp.notification.channels.configuration" - const val ERROR_NOTIFICATION_ID = 0 - const val CONFIG_ACCESS_NOTIFICATION_ID = 1 } init { @@ -65,9 +62,6 @@ class AppNotificationManager(private val context: Context) { NotificationManagerCompat.from(context).cancelAll() } - fun newConfigurationAccessNotificationBuilder() = - NotificationCompat.Builder(context, CONFIG_ACCESS_CHANNEL_ID) - @RequiresApi(Build.VERSION_CODES.O) private fun registerChannels() { context.getSystemService(NotificationManager::class.java) @@ -78,13 +72,6 @@ class AppNotificationManager(private val context: Context) { NotificationManager.IMPORTANCE_HIGH )) } - .apply { - createNotificationChannel(NotificationChannel( - CONFIG_ACCESS_CHANNEL_ID, - context.getString(R.string.notification_config_access_channel_name), - NotificationManager.IMPORTANCE_MIN - )) - } } private fun NotificationCompat.Builder.setHighPriority() = apply { diff --git a/app/src/main/java/org/pacien/tincapp/service/ConfigurationAccessService.kt b/app/src/main/java/org/pacien/tincapp/service/ConfigurationAccessService.kt deleted file mode 100644 index 916f19d..0000000 --- a/app/src/main/java/org/pacien/tincapp/service/ConfigurationAccessService.kt +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Tinc App, an Android binding and user interface for the tinc mesh VPN daemon - * Copyright (C) 2017-2023 Pacien TRAN-GIRARD - * - * This program 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. - * - * This program 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 this program. If not, see . - */ - -package org.pacien.tincapp.service - -import android.app.Service -import android.content.Context -import android.content.Intent -import android.os.IBinder -import androidx.databinding.ObservableBoolean -import ch.qos.logback.classic.Level -import ch.qos.logback.classic.Logger -import org.apache.ftpserver.ConnectionConfigFactory -import org.apache.ftpserver.DataConnectionConfigurationFactory -import org.apache.ftpserver.FtpServer -import org.apache.ftpserver.FtpServerFactory -import org.apache.ftpserver.ftplet.* -import org.apache.ftpserver.listener.ListenerFactory -import org.apache.ftpserver.usermanager.UsernamePasswordAuthentication -import org.apache.ftpserver.usermanager.impl.WritePermission -import org.pacien.tincapp.R -import org.pacien.tincapp.activities.configure.ConfigureActivity -import org.pacien.tincapp.context.App -import org.pacien.tincapp.context.AppNotificationManager -import org.pacien.tincapp.extensions.Java.defaultMessage -import org.pacien.tincapp.utils.PendingIntentUtils -import org.slf4j.LoggerFactory -import java.io.IOException - -/** - * FTP server service allowing a remote and local user to access and modify configuration files in - * the application's context. - * - * @author pacien - */ -class ConfigurationAccessService : Service() { - companion object { - // Apache Mina FtpServer's INFO log level is actually VERBOSE. - // The object holds static references to those loggers so that they stay around. - @Suppress("unused") - private val MINA_FTP_LOGGER_OVERRIDER = MinaLoggerOverrider(Level.WARN) - - private val context by lazy { App.getContext() } - private val store by lazy { context.getSharedPreferences("${this::class.java.`package`!!.name}.ftp", Context.MODE_PRIVATE)!! } - val runningState = ObservableBoolean(false) - - fun getFtpHomeDir(): String = context.applicationInfo.dataDir!! - fun getFtpUsername() = storeGetOrInsertString("username") { "tincapp" } - fun getFtpPassword() = storeGetOrInsertString("password") { generateRandomString(8) } - fun getFtpPort() = storeGetOrInsertInt("port") { 65521 } // tinc port `concat` FTP port - fun getFtpPassiveDataPorts() = storeGetOrInsertString("passive-range") { "65522-65532" } - - private fun storeGetOrInsertString(key: String, defaultGenerator: () -> String): String = synchronized(store) { - if (!store.contains(key)) store.edit().putString(key, defaultGenerator()).apply() - return store.getString(key, null)!! - } - - private fun storeGetOrInsertInt(key: String, defaultGenerator: () -> Int): Int = synchronized(store) { - if (!store.contains(key)) store.edit().putInt(key, defaultGenerator()).apply() - return store.getInt(key, 0) - } - - private fun generateRandomString(length: Int): String { - val alphabet = ('a'..'z') + ('A'..'Z') + ('0'..'9') - return List(length) { alphabet.random() }.joinToString("") - } - } - - private val log by lazy { LoggerFactory.getLogger(this.javaClass)!! } - private val notificationManager by lazy { App.notificationManager } - private var sftpServer: FtpServer? = null - - override fun onBind(intent: Intent): IBinder? = null // non-bindable service - - override fun onDestroy() { - sftpServer?.stop() - sftpServer = null - runningState.set(false) - log.info("Stopped FTP server") - super.onDestroy() - } - - override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { - val ftpUser = StaticFtpUser(getFtpUsername(), getFtpPassword(), getFtpHomeDir(), listOf(WritePermission())) - sftpServer = setupSingleUserServer(ftpUser, getFtpPort(), getFtpPassiveDataPorts()).also { - try { - it.start() - runningState.set(true) - log.info("Started FTP server on port {}", getFtpPort()) - pinInForeground() - } catch (e: IOException) { - log.error("Could not start FTP server", e) - App.alert(R.string.notification_error_title_unable_to_start_ftp_server, e.defaultMessage()) - } - } - - return START_NOT_STICKY - } - - /** - * Pins the service in the foreground so that it doesn't get stopped by the system when the - * application's activities are put in the background, which is the case when the user sets the - * focus on an FTP client app for example. - */ - private fun pinInForeground() { - startForeground( - AppNotificationManager.CONFIG_ACCESS_NOTIFICATION_ID, - notificationManager.newConfigurationAccessNotificationBuilder() - .setSmallIcon(R.drawable.ic_baseline_folder_open_primary_24dp) - .setContentTitle(resources.getString(R.string.notification_config_access_server_running_title)) - .setContentText(resources.getString(R.string.notification_config_access_server_running_message)) - .setContentIntent(Intent(this, ConfigureActivity::class.java).let { - PendingIntentUtils.getActivity(this, 0, it, 0) - }) - .build() - ) - } - - private fun setupSingleUserServer(ftpUser: User, ftpPort: Int, ftpPassivePorts: String): FtpServer = - FtpServerFactory() - .apply { - addListener("default", ListenerFactory() - .apply { - connectionConfig = ConnectionConfigFactory() - .apply { maxThreads = 1 } // library has issues with multiple threads - .createConnectionConfig() - } - .apply { port = ftpPort } - .apply { - dataConnectionConfiguration = DataConnectionConfigurationFactory() - .apply { passivePorts = ftpPassivePorts } - .createDataConnectionConfiguration() - } - .createListener() - ) - } - .apply { userManager = StaticFtpUserManager(listOf(ftpUser)) } - .createServer() - - private class StaticFtpUserManager(users: List) : UserManager { - private val userMap: Map = users.map { it.name to it }.toMap() - override fun getUserByName(username: String?): User? = userMap[username] - override fun getAllUserNames(): Array = userMap.keys.toTypedArray() - override fun doesExist(username: String?): Boolean = username in userMap - override fun delete(username: String?) = throw UnsupportedOperationException() - override fun save(user: User?) = throw UnsupportedOperationException() - override fun getAdminName(): String = throw UnsupportedOperationException() - override fun isAdmin(username: String?): Boolean = throw UnsupportedOperationException() - override fun authenticate(authentication: Authentication?): User = when (authentication) { - is UsernamePasswordAuthentication -> getUserByName(authentication.username).let { - if (it != null && authentication.password == it.password) it - else throw AuthenticationFailedException() - } - else -> throw IllegalArgumentException() - } - } - - private data class StaticFtpUser( - private val name: String, - private val password: String, - private val homeDirectory: String, - private val authorities: List - ) : User { - override fun getName(): String = name - override fun getPassword(): String = password - override fun getAuthorities(): List = authorities - override fun getAuthorities(clazz: Class): List = authorities.filter(clazz::isInstance) - override fun getMaxIdleTime(): Int = 0 // unlimited - override fun getEnabled(): Boolean = true - override fun getHomeDirectory(): String = homeDirectory - override fun authorize(request: AuthorizationRequest?): AuthorizationRequest? = - authorities.filter { it.canAuthorize(request) }.fold(request) { req, auth -> auth.authorize(req) } - } - - /** - * This registers package loggers filtering the output of the Mina FtpServer. - * The object holds static references to those loggers so that they stay around. - */ - private class MinaLoggerOverrider(logLevel: Level) { - @Suppress("unused") - private val ftpServerLogger = forceLogLevel("org.apache.ftpserver", logLevel) - - @Suppress("unused") - private val minaLogger = forceLogLevel("org.apache.mina", logLevel) - - private fun forceLogLevel(pkgName: String, logLevel: Level) = - (LoggerFactory.getLogger(pkgName) as Logger).apply { level = logLevel } - } -} diff --git a/app/src/main/res/layout/configure_activity.xml b/app/src/main/res/layout/configure_activity.xml index 08f29af..5d796fb 100644 --- a/app/src/main/res/layout/configure_activity.xml +++ b/app/src/main/res/layout/configure_activity.xml @@ -2,7 +2,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index 260caaa..1adbe0f 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -57,7 +57,6 @@ Ошибки Открыть руководство Не удалось запустить tinc - Не удалось запустить FTP-сервер Не удалось прочитать закрытые ключи tinc:\n%1$s Не удалось прочитать конфигурацию сетевого интерфейса:\n%1$s Не удалось привязать сетевой интерфейс. Запущен другой VPN? @@ -70,10 +69,6 @@ Недопустимая конфигурация сети в network.conf:\n%1$s Не удалось расшифровать закрытые ключи:\n%1$s - Доступ к конфигурации - Сервер доступа к конфигурации активен - Каталог конфигурации доступен через FTP. - Конфигурировать Трафик с этого устройства будет частично или полностью перенаправляться в выбранную сеть и через нее в соответствии с вашей конфигурацией. Никогда не подключайтесь к сети tinc, которой вы не доверяете. @@ -85,11 +80,6 @@ Недопустимое имя сети. Записана конфигурация сети. - Доступ к конфигурации - Доступ по FTP - Пользователь: %1$s, пароль: %2$s, порт: %3$d - Не активный - Информация о внутренних путях Каталог конфигурации Каталог журналов diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e124055..c4384a7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -62,7 +62,6 @@ Errors Open manual Could not start tinc - Could not start FTP server Could not read private tinc keys:\n%1$s Could not read network interface configuration:\n%1$s Could not bind network interface. Is another VPN running? @@ -75,10 +74,6 @@ Invalid network configuration in network.conf:\n%1$s Could not decrypt private keys:\n%1$s - Configuration access - Configuration access server active - The configuration directory is accessible through FTP. - Configure Traffic from this device will be partially or fully routed to and through the selected network in accordance with your configuration. Never connect to a tinc network which you do not trust. @@ -90,11 +85,6 @@ Invalid network name. Network configuration written. - Configuration access - FTP access - User: %1$s, password: %2$s, port: %3$d - Not active - Internal paths info Configuration directory Log directory diff --git a/readme.md b/readme.md index 853ae62..378337f 100644 --- a/readme.md +++ b/readme.md @@ -61,7 +61,6 @@ Builds of this software embed and make use of the following libraries: * logback-android, licensed under the GNU Lesser General Public License v2.1 * Apache Commons Configuration, licensed under the Apache v2.0 License * Apache Commons BeanUtils, licensed under the Apache v2.0 License -* Apache Mina FtpServer, licensed under the Apache v2.0 License * LZO, licensed under the GNU General Public License v2.0 * LibreSSL libcrypto, licensed under the OpenSSL License, ISC License, public domain -- cgit v1.2.3