Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-24 18:55:42 +01:00
parent a629de6271
commit 3cef7c5092
2161 changed files with 246605 additions and 2 deletions

View file

@ -0,0 +1,11 @@
@Suppress("DSL_SCOPE_VIOLATION")
plugins {
id(ThunderbirdPlugins.Library.jvm)
alias(libs.plugins.android.lint)
}
dependencies {
api(projects.app.autodiscovery.api)
implementation(libs.minidns.hla)
}

View file

@ -0,0 +1,29 @@
package com.fsck.k9.autodiscovery.srvrecords
import com.fsck.k9.mail.ConnectionSecurity.SSL_TLS_REQUIRED
import com.fsck.k9.mail.ConnectionSecurity.STARTTLS_REQUIRED
import org.minidns.dnslabel.DnsLabel
import org.minidns.dnsname.DnsName
import org.minidns.hla.ResolverApi
import org.minidns.hla.srv.SrvProto
class MiniDnsSrvResolver : SrvResolver {
override fun lookup(domain: String, type: SrvType): List<MailService> {
val result = ResolverApi.INSTANCE.resolveSrv(
DnsLabel.from(type.label),
SrvProto.tcp.dnsLabel,
DnsName.from(domain)
)
val security = if (type.assumeTls) SSL_TLS_REQUIRED else STARTTLS_REQUIRED
return result.answersOrEmptySet.map {
MailService(
srvType = type,
host = it.target.toString(),
port = it.port,
priority = it.priority,
security = security
)
}
}
}

View file

@ -0,0 +1,5 @@
package com.fsck.k9.autodiscovery.srvrecords
interface SrvResolver {
fun lookup(domain: String, type: SrvType): List<MailService>
}

View file

@ -0,0 +1,56 @@
package com.fsck.k9.autodiscovery.srvrecords
import com.fsck.k9.autodiscovery.api.ConnectionSettingsDiscovery
import com.fsck.k9.autodiscovery.api.DiscoveredServerSettings
import com.fsck.k9.autodiscovery.api.DiscoveryResults
import com.fsck.k9.helper.EmailHelper
import com.fsck.k9.mail.AuthType
import com.fsck.k9.mail.ConnectionSecurity
class SrvServiceDiscovery(
private val srvResolver: MiniDnsSrvResolver
) : ConnectionSettingsDiscovery {
override fun discover(email: String): DiscoveryResults? {
val domain = EmailHelper.getDomainFromEmailAddress(email) ?: return null
val mailServicePriority = compareBy<MailService> { it.priority }.thenByDescending { it.security }
val outgoingSettings = listOf(SrvType.SUBMISSIONS, SrvType.SUBMISSION)
.flatMap { srvResolver.lookup(domain, it) }
.sortedWith(mailServicePriority)
.map { newServerSettings(it, email) }
val incomingSettings = listOf(SrvType.IMAPS, SrvType.IMAP)
.flatMap { srvResolver.lookup(domain, it) }
.sortedWith(mailServicePriority)
.map { newServerSettings(it, email) }
return DiscoveryResults(incoming = incomingSettings, outgoing = outgoingSettings)
}
}
fun newServerSettings(service: MailService, email: String): DiscoveredServerSettings {
return DiscoveredServerSettings(
service.srvType.protocol,
service.host,
service.port,
service.security,
AuthType.PLAIN,
email
)
}
enum class SrvType(val label: String, val protocol: String, val assumeTls: Boolean) {
SUBMISSIONS("_submissions", "smtp", true),
SUBMISSION("_submission", "smtp", false),
IMAPS("_imaps", "imap", true),
IMAP("_imap", "imap", false)
}
data class MailService(
val srvType: SrvType,
val host: String,
val port: Int,
val priority: Int,
val security: ConnectionSecurity
)

View file

@ -0,0 +1,178 @@
package com.fsck.k9.autodiscovery.srvrecords
import com.fsck.k9.autodiscovery.api.DiscoveryResults
import com.fsck.k9.mail.ConnectionSecurity
import org.junit.Assert.assertEquals
import org.junit.Test
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
class SrvServiceDiscoveryTest {
@Test
fun discover_whenNoMailServices_shouldReturnNoResults() {
val srvResolver = newMockSrvResolver()
val srvServiceDiscovery = SrvServiceDiscovery(srvResolver)
val result = srvServiceDiscovery.discover("test@example.com")
assertEquals(DiscoveryResults(listOf(), listOf()), result)
}
@Test
fun discover_whenNoSMTP_shouldReturnJustIMAP() {
val srvResolver = newMockSrvResolver(
imapServices = listOf(newMailService(port = 143, srvType = SrvType.IMAP)),
imapsServices = listOf(
newMailService(port = 993, srvType = SrvType.IMAPS, security = ConnectionSecurity.SSL_TLS_REQUIRED)
)
)
val srvServiceDiscovery = SrvServiceDiscovery(srvResolver)
val result = srvServiceDiscovery.discover("test@example.com")
assertEquals(2, result!!.incoming.size)
assertEquals(0, result.outgoing.size)
}
@Test
fun discover_whenNoIMAP_shouldReturnJustSMTP() {
val srvResolver = newMockSrvResolver(
submissionServices = listOf(
newMailService(
port = 25,
srvType = SrvType.SUBMISSION,
security = ConnectionSecurity.STARTTLS_REQUIRED
),
newMailService(
port = 465,
srvType = SrvType.SUBMISSIONS,
security = ConnectionSecurity.SSL_TLS_REQUIRED
)
)
)
val srvServiceDiscovery = SrvServiceDiscovery(srvResolver)
val result = srvServiceDiscovery.discover("test@example.com")
assertEquals(0, result!!.incoming.size)
assertEquals(2, result.outgoing.size)
}
@Test
fun discover_withRequiredServices_shouldCorrectlyPrioritize() {
val srvResolver = newMockSrvResolver(
submissionServices = listOf(
newMailService(
host = "smtp1.example.com",
port = 25,
srvType = SrvType.SUBMISSION,
security = ConnectionSecurity.STARTTLS_REQUIRED,
priority = 0
),
newMailService(
host = "smtp2.example.com",
port = 25,
srvType = SrvType.SUBMISSION,
security = ConnectionSecurity.STARTTLS_REQUIRED,
priority = 1
)
),
submissionsServices = listOf(
newMailService(
host = "smtp3.example.com",
port = 465,
srvType = SrvType.SUBMISSIONS,
security = ConnectionSecurity.SSL_TLS_REQUIRED,
priority = 0
),
newMailService(
host = "smtp4.example.com",
port = 465,
srvType = SrvType.SUBMISSIONS,
security = ConnectionSecurity.SSL_TLS_REQUIRED,
priority = 1
)
),
imapServices = listOf(
newMailService(
host = "imap1.example.com",
port = 143,
srvType = SrvType.IMAP,
security = ConnectionSecurity.STARTTLS_REQUIRED,
priority = 0
),
newMailService(
host = "imap2.example.com",
port = 143,
srvType = SrvType.IMAP,
security = ConnectionSecurity.STARTTLS_REQUIRED,
priority = 1
)
),
imapsServices = listOf(
newMailService(
host = "imaps1.example.com",
port = 993,
srvType = SrvType.IMAPS,
security = ConnectionSecurity.SSL_TLS_REQUIRED,
priority = 0
),
newMailService(
host = "imaps2.example.com",
port = 993,
srvType = SrvType.IMAPS,
security = ConnectionSecurity.SSL_TLS_REQUIRED,
priority = 1
)
)
)
val srvServiceDiscovery = SrvServiceDiscovery(srvResolver)
val result = srvServiceDiscovery.discover("test@example.com")
assertEquals(
listOf(
"smtp3.example.com",
"smtp1.example.com",
"smtp4.example.com",
"smtp2.example.com"
),
result?.outgoing?.map { it.host }
)
assertEquals(
listOf(
"imaps1.example.com",
"imap1.example.com",
"imaps2.example.com",
"imap2.example.com"
),
result?.incoming?.map { it.host }
)
}
private fun newMailService(
host: String = "example.com",
priority: Int = 0,
security: ConnectionSecurity = ConnectionSecurity.STARTTLS_REQUIRED,
srvType: SrvType,
port: Int
): MailService {
return MailService(srvType, host, port, priority, security)
}
private fun newMockSrvResolver(
host: String = "example.com",
submissionServices: List<MailService> = listOf(),
submissionsServices: List<MailService> = listOf(),
imapServices: List<MailService> = listOf(),
imapsServices: List<MailService> = listOf()
): MiniDnsSrvResolver {
return mock {
on { lookup(host, SrvType.SUBMISSION) } doReturn submissionServices
on { lookup(host, SrvType.SUBMISSIONS) } doReturn submissionsServices
on { lookup(host, SrvType.IMAP) } doReturn imapServices
on { lookup(host, SrvType.IMAPS) } doReturn imapsServices
}
}
}