Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 13:56:56 +01:00
parent 75dc487a7a
commit 39c29d175b
6317 changed files with 388324 additions and 2 deletions

View file

@ -0,0 +1,18 @@
plugins {
id(ThunderbirdPlugins.App.jvm)
}
version = "unspecified"
application {
mainClass.set("app.k9mail.cli.autodiscovery.MainKt")
}
dependencies {
implementation(projects.feature.autodiscovery.api)
implementation(projects.feature.autodiscovery.autoconfig)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.clikt)
implementation(libs.kxml2)
}

View file

@ -0,0 +1,70 @@
package app.k9mail.cli.autodiscovery
import app.k9mail.autodiscovery.api.AutoDiscoveryResult
import app.k9mail.autodiscovery.api.AutoDiscoveryResult.Settings
import app.k9mail.autodiscovery.autoconfig.AutoconfigUrlConfig
import app.k9mail.autodiscovery.autoconfig.createIspDbAutoconfigDiscovery
import app.k9mail.autodiscovery.autoconfig.createMxLookupAutoconfigDiscovery
import app.k9mail.autodiscovery.autoconfig.createProviderAutoconfigDiscovery
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.Context
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option
import kotlin.time.measureTimedValue
import kotlinx.coroutines.runBlocking
import net.thunderbird.core.common.mail.toUserEmailAddress
import okhttp3.OkHttpClient.Builder
class AutoDiscoveryCli : CliktCommand() {
private val httpsOnly by option(help = "Only perform Autoconfig lookups using HTTPS").flag()
private val includeEmailAddress by option(help = "Include email address in Autoconfig lookups").flag()
private val emailAddress by argument(name = "email", help = "Email address")
override fun help(context: Context) =
"Performs the auto-discovery steps used by Thunderbird for Android to find mail server settings"
override fun run() {
echo("Attempting to find mail server settings for <$emailAddress>…")
echo()
val config = AutoconfigUrlConfig(
httpsOnly = httpsOnly,
includeEmailAddress = includeEmailAddress,
)
val (discoveryResult, duration) = measureTimedValue {
runAutoDiscovery(config)
}
if (discoveryResult is Settings) {
echo("Found the following mail server settings:")
AutoDiscoveryResultFormatter(::echo).output(discoveryResult)
} else {
echo("Couldn't find any mail server settings.")
}
echo()
echo("Duration: ${duration.inWholeMilliseconds}")
}
private fun runAutoDiscovery(config: AutoconfigUrlConfig): AutoDiscoveryResult {
val okHttpClient = Builder().build()
try {
val providerDiscovery = createProviderAutoconfigDiscovery(okHttpClient, config)
val ispDbDiscovery = createIspDbAutoconfigDiscovery(okHttpClient)
val mxDiscovery = createMxLookupAutoconfigDiscovery(okHttpClient, config)
val runnables = listOf(providerDiscovery, ispDbDiscovery, mxDiscovery)
.flatMap { it.initDiscovery(emailAddress.toUserEmailAddress()) }
val serialRunner = SerialRunner(runnables)
return runBlocking {
serialRunner.run()
}
} finally {
okHttpClient.dispatcher.executorService.shutdown()
}
}
}

View file

@ -0,0 +1,35 @@
package app.k9mail.cli.autodiscovery
import app.k9mail.autodiscovery.api.AutoDiscoveryResult.Settings
import app.k9mail.autodiscovery.api.ImapServerSettings
import app.k9mail.autodiscovery.api.SmtpServerSettings
internal class AutoDiscoveryResultFormatter(private val echo: (String) -> Unit) {
fun output(settings: Settings) {
val incomingServer = requireNotNull(settings.incomingServerSettings as? ImapServerSettings)
val outgoingServer = requireNotNull(settings.outgoingServerSettings as? SmtpServerSettings)
echo("------------------------------")
echo("Source: ${settings.source}")
echo("")
echo("Incoming server:")
echo(" Hostname: ${incomingServer.hostname.value}")
echo(" Port: ${incomingServer.port.value}")
echo(" Connection security: ${incomingServer.connectionSecurity}")
echo(" Authentication: ${incomingServer.authenticationTypes.joinToString()}")
echo(" Username: ${incomingServer.username}")
echo("")
echo("Outgoing server:")
echo(" Hostname: ${outgoingServer.hostname.value}")
echo(" Port: ${outgoingServer.port.value}")
echo(" Connection security: ${outgoingServer.connectionSecurity}")
echo(" Authentication: ${outgoingServer.authenticationTypes.joinToString()}")
echo(" Username: ${outgoingServer.username}")
echo("------------------------------")
if (settings.isTrusted) {
echo("These settings have been retrieved through trusted channels.")
} else {
echo("At least one UNTRUSTED channel was involved in retrieving these settings.")
}
}
}

View file

@ -0,0 +1,5 @@
package app.k9mail.cli.autodiscovery
import com.github.ajalt.clikt.core.main
fun main(args: Array<String>) = AutoDiscoveryCli().main(args)

View file

@ -0,0 +1,43 @@
package app.k9mail.cli.autodiscovery
import app.k9mail.autodiscovery.api.AutoDiscoveryResult
import app.k9mail.autodiscovery.api.AutoDiscoveryResult.NetworkError
import app.k9mail.autodiscovery.api.AutoDiscoveryResult.NoUsableSettingsFound
import app.k9mail.autodiscovery.api.AutoDiscoveryResult.Settings
import app.k9mail.autodiscovery.api.AutoDiscoveryResult.UnexpectedException
import app.k9mail.autodiscovery.api.AutoDiscoveryRunnable
import net.thunderbird.core.logging.legacy.Log
/**
* Run a list of [AutoDiscoveryRunnable] one after the other until one returns a [Settings] result.
*/
class SerialRunner(private val runnables: List<AutoDiscoveryRunnable>) {
suspend fun run(): AutoDiscoveryResult {
var networkErrorCount = 0
var networkError: NetworkError? = null
for (runnable in runnables) {
when (val discoveryResult = runnable.run()) {
is Settings -> {
return discoveryResult
}
is NetworkError -> {
networkErrorCount++
if (networkError == null) {
networkError = discoveryResult
}
}
NoUsableSettingsFound -> { }
is UnexpectedException -> {
Log.w(discoveryResult.exception, "Unexpected exception")
}
}
}
return if (networkError != null && networkErrorCount == runnables.size) {
networkError
} else {
NoUsableSettingsFound
}
}
}