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. --- .../configure/ConfigurationAccessServerFragment.kt | 77 -------- .../tincapp/context/AppNotificationManager.kt | 13 -- .../tincapp/service/ConfigurationAccessService.kt | 204 --------------------- 3 files changed, 294 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 (limited to 'app/src/main/java/org') 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 } - } -} -- cgit v1.2.3