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