From 5e0d52b9e7d29c4259653ad9c6dbdea444926a9f Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Mon, 5 Feb 2024 19:56:58 +0100 Subject: [PATCH] Added domain autconfig detection of @benbucksch https://datatracker.ietf.org/doc/draft-bucksch-autoconfig/ --- dev/View/Popup/Domain.js | 22 ++++ .../RainLoop/Actions/AdminDomains.php | 11 ++ .../RainLoop/Providers/Domain/Autoconfig.php | 108 ++++++++++++++++++ .../v/0.0.0/app/libraries/snappymail/dns.php | 16 +++ .../templates/Views/Admin/PopupsDomain.html | 1 + 5 files changed, 158 insertions(+) create mode 100644 snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Domain/Autoconfig.php diff --git a/dev/View/Popup/Domain.js b/dev/View/Popup/Domain.js index 684d3bb38..25d5b333f 100644 --- a/dev/View/Popup/Domain.js +++ b/dev/View/Popup/Domain.js @@ -306,6 +306,28 @@ export class DomainPopupView extends AbstractViewPopup { this.testingSmtpError(false); } + autoconfig() { + let domain = this.name(); + Remote.request('AdminDomainAutoconfig', (iError, oData) => { + console.dir({iError, oData}); + if (!iError) { + let server = oData.Result.config.incomingServer[0]; + this.imapHost(server.hostname); + this.imapPort(server.port); + this.imapType('STARTTLS' === server.socketType ? 2 : ('SSL' === server.socketType ? 1 : 0)); + this.imapShortLogin('%EMAILADDRESS%' !== server.username); + + server = oData.Result.config.outgoingServer[0]; + this.smtpHost(server.hostname); + this.smtpPort(server.port); + this.smtpType('STARTTLS' === server.socketType ? 2 : ('SSL' === server.socketType ? 1 : 0)); + this.smtpShortLogin('%EMAILADDRESS%' !== server.username); + this.smtpUseAuth(!!server.authentication); + this.smtpUsePhpMail(false); + } + }, {domain}); + } + onShow(oDomain) { this.saving(false); this.clearTesting(); diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/AdminDomains.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/AdminDomains.php index 42b084800..c7b1bc35d 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/AdminDomains.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/AdminDomains.php @@ -74,6 +74,17 @@ trait AdminDomains )); } + public function DoAdminDomainAutoconfig() : array + { + $this->IsAdminLoggined(); + $sDomain = $this->GetActionParam('domain'); + $sEmail = "test@{$sDomain}"; + return $this->DefaultResponse(array( + 'email' => $sEmail, + 'config' => \RainLoop\Providers\Domain\Autoconfig::discover($sEmail) + )); + } + public function DoAdminDomainTest() : array { $this->IsAdminLoggined(); diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Domain/Autoconfig.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Domain/Autoconfig.php new file mode 100644 index 000000000..c07a78c56 --- /dev/null +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Domain/Autoconfig.php @@ -0,0 +1,108 @@ + \substr_count($mxbasedomain, '.')) { + // Remove the first component from the MX hostname + $mxfulldomain = \explode('.', $hostname, 2)[1]; + } + $hostnames[$mxfulldomain] = $mxbasedomain; + } + } + foreach ($hostnames as $mxfulldomain => $mxbasedomain) { + $autoconfig = static::resolve($mxfulldomain, $emailaddress); + if (!$autoconfig && $mxfulldomain != $mxbasedomain) { + $autoconfig = static::resolve($mxbasedomain, $emailaddress); + } + if ($autoconfig) { + break; + } + } + } + return $autoconfig; + } + + private static function resolve(string $domain, string $emailaddress) + { + $emailaddress = \urlencode($emailaddress); + foreach ([ + // 4.1. Mail provider + "https://autoconfig.{$domain}/.well-known/mail-v1.xml?emailaddress={$emailaddress}", + "https://{$domain}/.well-known/autoconfig/mail/config-v1.1.xml", + "http://autoconfig.{$domain}/mail/config-v1.1.xml", + // 4.2. Central database + "https://autoconfig.thunderbird.net/v1.1/{$domain}" + ] as $url) { + $data = \file_get_contents($url); + if ($data && \str_contains($data, ' 'imap' === $data['@attributes']['type'] + ); + $data['outgoingServer'] = \array_filter( + isset($data['outgoingServer'][0]) ? $data['outgoingServer'] : [$data['outgoingServer']], + fn($data) => 'smtp' === $data['@attributes']['type'] + ); + return $data; + } + } + } + } + + private static function publicsuffixes() : array + { + $oCache = \RainLoop\Api::Actions()->Cacher(); + $list = $oCache->Get('public_suffix_list') ?: null; + if ($list) { + $list = \json_decode($list); + if ($list[1] < \time()) { + $list = null; + } else { + $list = $list[0]; + } + } + if (null === $list) { + $list = []; + $data = \file_get_contents('https://publicsuffix.org/list/public_suffix_list.dat'); + if ($data) { + $list = \array_filter( + \explode("\n", $data), + fn($text) => \strlen($text) && '/' !== $text[0] + ); + } + // Don't lookup for 24 hours + $oCache->Set('public_suffix_list', \json_encode([$list, time() + 86400])); + } + return $list ?: []; + } + +} diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/dns.php b/snappymail/v/0.0.0/app/libraries/snappymail/dns.php index c48f4f1c0..7f55dad28 100644 --- a/snappymail/v/0.0.0/app/libraries/snappymail/dns.php +++ b/snappymail/v/0.0.0/app/libraries/snappymail/dns.php @@ -48,4 +48,20 @@ abstract class DNS } return $BIMI; } + + public static function MX(string $domain) : array + { + $mxhosts = array(); + $values = \dns_get_record($domain, \DNS_MX); + if ($values) { + foreach ($values as $record) { + $mxhosts[$record['pri']] = $record['target']; + } + } + if (!$mxhosts) { + \getmxrr($hostname, $mxhosts); + } + \ksort($mxhosts); + return \array_values($mxhosts); + } } diff --git a/snappymail/v/0.0.0/app/templates/Views/Admin/PopupsDomain.html b/snappymail/v/0.0.0/app/templates/Views/Admin/PopupsDomain.html index fb799e2f8..b05286b60 100644 --- a/snappymail/v/0.0.0/app/templates/Views/Admin/PopupsDomain.html +++ b/snappymail/v/0.0.0/app/templates/Views/Admin/PopupsDomain.html @@ -11,6 +11,7 @@
+