Added domain autconfig detection of @benbucksch

https://datatracker.ietf.org/doc/draft-bucksch-autoconfig/
This commit is contained in:
the-djmaze 2024-02-05 19:56:58 +01:00
parent d4cdfb8fa5
commit 5e0d52b9e7
5 changed files with 158 additions and 0 deletions

View file

@ -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();

View file

@ -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();

View file

@ -0,0 +1,108 @@
<?php
/**
* https://datatracker.ietf.org/doc/draft-bucksch-autoconfig/
*/
namespace RainLoop\Providers\Domain;
abstract class Autoconfig
{
public static function discover(string $emailaddress)
{
$domain = \explode('@', $emailaddress, 2)[1];
// First try
$autoconfig = static::resolve($domain, $emailaddress);
// Else try MX
if (!$autoconfig) {
$regex = '/([^\.]+\.(?:'
. \str_replace('.', '\\.', \implode('|', static::publicsuffixes()))
. '|[^\.]+'
. '))$/';
$hostnames = [];
foreach (\SnappyMail\DNS::MX($domain) as $hostname) {
$mxbasedomain = [];
// Extract only the second-level domain from the MX hostname
if (\preg_match($regex, $hostname, $mxbasedomain)) {
$mxfulldomain = $mxbasedomain = $mxbasedomain[1];
if (\substr_count($hostname, '.') > \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, '<clientConfig')) {
$data = \json_decode(
\json_encode(
\simplexml_load_string($data, 'SimpleXMLElement', \LIBXML_NOCDATA)
), true);
if (!empty($data['emailProvider'])) {
$data = $data['emailProvider'];
unset($data['documentation']);
$data['incomingServer'] = \array_filter(
isset($data['incomingServer'][0]) ? $data['incomingServer'] : [$data['incomingServer']],
fn($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 ?: [];
}
}

View file

@ -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);
}
}

View file

@ -11,6 +11,7 @@
<input name="Name" type="text" class="span4" autofocus="" autocomplete="off" autocorrect="off" autocapitalize="off"
data-bind="textInput: name">
<div class="alert-error" data-bind="visible: savingError, text: savingError"></div>
<button type="button" data-bind="click: autoconfig">autoconfig</button>
</div>
<div class="span5 domain-desc" data-bind="visible: domainDesc, html: domainDesc"></div>
</div>