Merge pull request #4719 from livmackintosh

Git history was manually cleaned up.
This commit is contained in:
cketti 2020-05-09 17:51:22 +02:00
commit 259d0d03fe
7 changed files with 211 additions and 0 deletions

View file

@ -10,6 +10,7 @@ dependencies {
implementation "com.jakewharton.timber:timber:${versions.timber}"
implementation "com.squareup.okhttp3:okhttp:${versions.okhttp}"
implementation "org.minidns:minidns-hla:${versions.minidns}"
testImplementation project(':app:testing')
testImplementation project(":backend:imap")

View file

@ -2,9 +2,13 @@ package com.fsck.k9.autodiscovery
import com.fsck.k9.autodiscovery.providersxml.ProvidersXmlDiscovery
import com.fsck.k9.autodiscovery.providersxml.ProvidersXmlProvider
import com.fsck.k9.autodiscovery.srvrecords.MiniDnsSrvResolver
import com.fsck.k9.autodiscovery.srvrecords.SrvServiceDiscovery
import org.koin.dsl.module
val autodiscoveryModule = module {
factory { ProvidersXmlProvider(get()) }
factory { ProvidersXmlDiscovery(get(), get()) }
single { MiniDnsSrvResolver() }
factory { SrvServiceDiscovery(get()) }
}

View file

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

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,60 @@
package com.fsck.k9.autodiscovery.srvrecords
import com.fsck.k9.autodiscovery.ConnectionSettings
import com.fsck.k9.autodiscovery.ConnectionSettingsDiscovery
import com.fsck.k9.helper.EmailHelper
import com.fsck.k9.mail.AuthType
import com.fsck.k9.mail.ConnectionSecurity
import com.fsck.k9.mail.ServerSettings
class SrvServiceDiscovery(
private val srvResolver: MiniDnsSrvResolver
) : ConnectionSettingsDiscovery {
override fun discover(email: String): ConnectionSettings? {
val domain = EmailHelper.getDomainFromEmailAddress(email) ?: return null
val pickMailService = compareBy<MailService> { it.priority }.thenByDescending { it.security }
val outgoingService = listOf(SrvType.SUBMISSIONS, SrvType.SUBMISSION).flatMap { srvResolver.lookup(domain, it) }
.minWith(pickMailService) ?: return null
val incomingService = listOf(SrvType.IMAPS, SrvType.IMAP).flatMap { srvResolver.lookup(domain, it) }
.minWith(pickMailService) ?: return null
return ConnectionSettings(
incoming = ServerSettings(
incomingService.srvType.protocol,
incomingService.host,
incomingService.port,
incomingService.security,
AuthType.PLAIN,
email,
null,
null
),
outgoing = ServerSettings(
outgoingService.srvType.protocol,
outgoingService.host,
outgoingService.port,
outgoingService.security,
AuthType.PLAIN,
email,
null,
null
)
)
}
}
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,112 @@
package com.fsck.k9.autodiscovery.srvrecords
import com.fsck.k9.RobolectricTest
import com.fsck.k9.mail.ConnectionSecurity
import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.mock
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test
class SrvServiceDiscoveryTest : RobolectricTest() {
@Test
fun discover_whenNoMailServices_shouldReturnNull() {
val srvResolver = newMockSrvResolver()
val srvServiceDiscovery = SrvServiceDiscovery(srvResolver)
val result = srvServiceDiscovery.discover("test@example.com")
assertNull(result)
}
@Test
fun discover_whenNoSMTP_shouldReturnNull() {
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")
assertNull(result)
}
@Test
fun discover_whenNoIMAP_shouldReturnNull() {
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")
assertNull(result)
}
@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("smtp3.example.com", result?.outgoing?.host)
assertEquals("imaps1.example.com", result?.incoming?.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
}
}
}

View file

@ -32,6 +32,7 @@ buildscript {
'commonsIo': '2.6',
'mime4j': '0.8.1',
'okhttp': '4.5.0',
'minidns': '0.3.4',
'androidxTestRunner': '1.1.1',
'junit': '4.13',