mirror of
https://github.com/the-djmaze/snappymail.git
synced 2025-09-21 20:44:25 +08:00
Bugfix: handle multiple DKIM signatures authentication results
This commit is contained in:
parent
219b155ede
commit
315f2a345a
7 changed files with 90 additions and 217 deletions
|
@ -259,14 +259,12 @@ export class EmailModel extends AbstractModel {
|
|||
* @param {string=} email = ''
|
||||
* @param {string=} name = ''
|
||||
* @param {string=} dkimStatus = 'none'
|
||||
* @param {string=} dkimValue = ''
|
||||
*/
|
||||
constructor(email = '', name = '', dkimStatus = 'none', dkimValue = '') {
|
||||
constructor(email = '', name = '', dkimStatus = 'none') {
|
||||
super();
|
||||
this.email = email;
|
||||
this.name = name;
|
||||
this.dkimStatus = dkimStatus;
|
||||
this.dkimValue = dkimValue;
|
||||
|
||||
this.clearDuplicateName();
|
||||
}
|
||||
|
@ -290,7 +288,6 @@ export class EmailModel extends AbstractModel {
|
|||
this.name = '';
|
||||
|
||||
this.dkimStatus = 'none';
|
||||
this.dkimValue = '';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -5,7 +5,7 @@ import { i18n } from 'Common/Translator';
|
|||
|
||||
import { doc, SettingsGet } from 'Common/Globals';
|
||||
import { encodeHtml, plainToHtml, htmlToPlain, cleanHtml } from 'Common/Html';
|
||||
import { arrayLength, forEachObjectEntry } from 'Common/Utils';
|
||||
import { isFunction, forEachObjectEntry } from 'Common/Utils';
|
||||
import { serverRequestRaw, proxy } from 'Common/Links';
|
||||
import { addObservablesTo, addComputablesTo } from 'External/ko';
|
||||
|
||||
|
@ -66,7 +66,24 @@ export class MessageModel extends AbstractModel {
|
|||
constructor() {
|
||||
super();
|
||||
|
||||
this._reset();
|
||||
this.folder = '';
|
||||
this.uid = 0;
|
||||
this.hash = '';
|
||||
this.requestHash = '';
|
||||
this.from = new EmailCollectionModel;
|
||||
this.to = new EmailCollectionModel;
|
||||
this.cc = new EmailCollectionModel;
|
||||
this.bcc = new EmailCollectionModel;
|
||||
this.replyTo = new EmailCollectionModel;
|
||||
this.deliveredTo = new EmailCollectionModel;
|
||||
this.body = null;
|
||||
this.draftInfo = [];
|
||||
this.dkim = [];
|
||||
this.spf = [];
|
||||
this.dmarc = [];
|
||||
this.messageId = '';
|
||||
this.inReplyTo = '';
|
||||
this.references = '';
|
||||
|
||||
addObservablesTo(this, {
|
||||
subject: '',
|
||||
|
@ -156,67 +173,6 @@ export class MessageModel extends AbstractModel {
|
|||
toggleTag(this, keyword);
|
||||
}
|
||||
|
||||
_reset() {
|
||||
this.folder = '';
|
||||
this.uid = 0;
|
||||
this.hash = '';
|
||||
this.requestHash = '';
|
||||
this.emails = [];
|
||||
this.from = new EmailCollectionModel;
|
||||
this.to = new EmailCollectionModel;
|
||||
this.cc = new EmailCollectionModel;
|
||||
this.bcc = new EmailCollectionModel;
|
||||
this.replyTo = new EmailCollectionModel;
|
||||
this.deliveredTo = new EmailCollectionModel;
|
||||
this.body = null;
|
||||
this.draftInfo = [];
|
||||
this.messageId = '';
|
||||
this.inReplyTo = '';
|
||||
this.references = '';
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._reset();
|
||||
this.subject('');
|
||||
this.html('');
|
||||
this.plain('');
|
||||
this.size(0);
|
||||
this.spamScore(0);
|
||||
this.spamResult('');
|
||||
this.isSpam(false);
|
||||
this.hasVirus(null);
|
||||
this.dateTimeStampInUTC(0);
|
||||
this.priority(MessagePriority.Normal);
|
||||
|
||||
this.senderEmailsString('');
|
||||
this.senderClearEmailsString('');
|
||||
|
||||
this.deleted(false);
|
||||
|
||||
this.selected(false);
|
||||
this.checked(false);
|
||||
|
||||
this.isHtml(false);
|
||||
this.hasImages(false);
|
||||
this.hasExternals(false);
|
||||
this.attachments(new AttachmentCollectionModel);
|
||||
|
||||
this.pgpSigned(null);
|
||||
this.pgpVerified(null);
|
||||
|
||||
this.pgpEncrypted(null);
|
||||
this.pgpDecrypted(false);
|
||||
|
||||
this.priority(MessagePriority.Normal);
|
||||
this.readReceipt('');
|
||||
|
||||
this.threads([]);
|
||||
this.unsubsribeLinks([]);
|
||||
|
||||
this.hasUnseenSubMessage(false);
|
||||
this.hasFlaggedSubMessage(false);
|
||||
}
|
||||
|
||||
spamStatus() {
|
||||
let spam = this.spamResult();
|
||||
return spam ? i18n(this.isSpam() ? 'GLOBAL/SPAM' : 'GLOBAL/NOT_SPAM') + ': ' + spam : '';
|
||||
|
@ -276,18 +232,6 @@ export class MessageModel extends AbstractModel {
|
|||
return this.from.toString(friendlyView, wrapWithLink);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
fromDkimData() {
|
||||
let result = ['none', ''];
|
||||
if (1 === arrayLength(this.from) && this.from[0]?.dkimStatus) {
|
||||
result = [this.from[0].dkimStatus, this.from[0].dkimValue || ''];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} friendlyView
|
||||
* @param {boolean=} wrapWithLink
|
||||
|
@ -493,43 +437,14 @@ export class MessageModel extends AbstractModel {
|
|||
let self = new MessageModel();
|
||||
|
||||
if (message) {
|
||||
self.folder = message.folder;
|
||||
self.uid = message.uid;
|
||||
self.hash = message.hash;
|
||||
self.requestHash = message.requestHash;
|
||||
self.subject(message.subject());
|
||||
self.plain(message.plain());
|
||||
self.html(message.html());
|
||||
|
||||
self.size(message.size());
|
||||
self.spamScore(message.spamScore());
|
||||
self.spamResult(message.spamResult());
|
||||
self.isSpam(message.isSpam());
|
||||
self.hasVirus(message.hasVirus());
|
||||
self.dateTimeStampInUTC(message.dateTimeStampInUTC());
|
||||
self.priority(message.priority());
|
||||
|
||||
self.hasExternals(message.hasExternals());
|
||||
|
||||
self.emails = message.emails;
|
||||
|
||||
self.from = message.from;
|
||||
self.to = message.to;
|
||||
self.cc = message.cc;
|
||||
self.bcc = message.bcc;
|
||||
self.replyTo = message.replyTo;
|
||||
self.deliveredTo = message.deliveredTo;
|
||||
self.unsubsribeLinks(message.unsubsribeLinks);
|
||||
|
||||
self.flags(message.flags());
|
||||
|
||||
self.priority(message.priority());
|
||||
|
||||
self.selected(message.selected());
|
||||
self.checked(message.checked());
|
||||
self.attachments(message.attachments());
|
||||
|
||||
self.threads(message.threads());
|
||||
// Clone message values
|
||||
forEachObjectEntry(message, (key, value) => {
|
||||
if (ko.isObservable(value)) {
|
||||
ko.isComputed(value) || self[key](value());
|
||||
} else if (!isFunction(value)) {
|
||||
self[key] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
self.computeSenderEmail();
|
||||
|
|
|
@ -196,7 +196,9 @@ export class MailMessageView extends AbstractViewRight {
|
|||
this.viewHash = message.hash;
|
||||
// TODO: make first param a user setting #683
|
||||
this.viewFromShort(message.fromToLine(false, true));
|
||||
this.viewFromDkimData(message.fromDkimData());
|
||||
let dkim = 1 === arrayLength(message.from) && message.dkim
|
||||
&& message.dkim.find(dkim => message.from[0].email.includes(dkim[1]));
|
||||
this.viewFromDkimData(dkim ? [dkim[0], dkim[2]] : ['none', '']);
|
||||
this.viewToShort(message.toToLine(true, true));
|
||||
} else {
|
||||
MessagelistUserStore.selectedMessage(null);
|
||||
|
|
|
@ -63,6 +63,9 @@ class Message implements \JsonSerializable
|
|||
$bHasVirus = null;
|
||||
|
||||
private array
|
||||
$SPF = [],
|
||||
$DKIM = [],
|
||||
$DMARC = [],
|
||||
// $aFlags = [],
|
||||
$aFlagsLowerCase = [],
|
||||
$UnsubsribeLinks = [],
|
||||
|
@ -138,9 +141,8 @@ class Message implements \JsonSerializable
|
|||
$sCharset = $oBodyStructure ? Utils::NormalizeCharset($oBodyStructure->SearchCharset()) : '';
|
||||
|
||||
$sHeaders = $oFetchResponse->GetHeaderFieldsValue();
|
||||
if (\strlen($sHeaders)) {
|
||||
$oHeaders = new \MailSo\Mime\HeaderCollection($sHeaders, false, $sCharset);
|
||||
|
||||
$oHeaders = \strlen($sHeaders) ? new \MailSo\Mime\HeaderCollection($sHeaders, false, $sCharset) : null;
|
||||
if ($oHeaders) {
|
||||
$sContentTypeCharset = $oHeaders->ParameterValue(
|
||||
\MailSo\Mime\Enumerations\Header::CONTENT_TYPE,
|
||||
\MailSo\Mime\Enumerations\Parameter::CHARSET
|
||||
|
@ -165,10 +167,6 @@ class Message implements \JsonSerializable
|
|||
$oMessage->oCc = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::CC, $bCharsetAutoDetect);
|
||||
$oMessage->oBcc = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::BCC, $bCharsetAutoDetect);
|
||||
|
||||
if ($oMessage->oFrom) {
|
||||
$oHeaders->PopulateEmailColectionByDkim($oMessage->oFrom);
|
||||
}
|
||||
|
||||
$oMessage->oSender = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::SENDER, $bCharsetAutoDetect);
|
||||
$oMessage->oReplyTo = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::REPLY_TO, $bCharsetAutoDetect);
|
||||
$oMessage->oDeliveredTo = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::DELIVERED_TO, $bCharsetAutoDetect);
|
||||
|
@ -291,6 +289,21 @@ class Message implements \JsonSerializable
|
|||
}
|
||||
}
|
||||
|
||||
$aAuth = $oHeaders->AuthStatuses();
|
||||
$oMessage->SPF = $aAuth['spf'];
|
||||
$oMessage->DKIM = $aAuth['dkim'];
|
||||
$oMessage->DMARC = $aAuth['dmarc'];
|
||||
if ($aAuth['dkim'] && $oMessage->oFrom) {
|
||||
foreach ($oMessage->oFrom as $oEmail) {
|
||||
$sEmail = $oEmail->GetEmail();
|
||||
foreach ($aAuth['dkim'] as $aDkimData) {
|
||||
if (\strpos($sEmail, $aDkimData[1])) {
|
||||
$oEmail->SetDkimStatus($aDkimData[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$oMessage->sAutocrypt = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::AUTOCRYPT);
|
||||
}
|
||||
else if ($oFetchResponse->GetEnvelope())
|
||||
|
@ -331,7 +344,7 @@ class Message implements \JsonSerializable
|
|||
// /?/Raw/&q[]=/0/View/&q[]=/...
|
||||
'BodyPartId' => $oPart->SubParts()[0]->PartID(),
|
||||
'SigPartId' => $oPgpSignaturePart->PartID(),
|
||||
'MicAlg' => (string) $oHeaders->ParameterValue(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'micalg')
|
||||
'MicAlg' => $oHeaders ? (string) $oHeaders->ParameterValue(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'micalg') : ''
|
||||
];
|
||||
/*
|
||||
// An empty section specification refers to the entire message, including the header.
|
||||
|
@ -478,6 +491,10 @@ class Message implements \JsonSerializable
|
|||
|
||||
'Attachments' => $this->Attachments,
|
||||
|
||||
'spf' => $this->SPF,
|
||||
'dkim' => $this->DKIM,
|
||||
'dmarc' => $this->DMARC,
|
||||
|
||||
'Flags' => $aFlags,
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/rfc8621#section-4.1.1
|
||||
|
|
|
@ -23,8 +23,6 @@ class Email implements \JsonSerializable
|
|||
|
||||
private string $sDkimStatus = Enumerations\DkimStatus::NONE;
|
||||
|
||||
private string $sDkimValue = '';
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
|
@ -165,16 +163,6 @@ class Email implements \JsonSerializable
|
|||
return $this->sDisplayName;
|
||||
}
|
||||
|
||||
public function GetDkimStatus() : string
|
||||
{
|
||||
return $this->sDkimStatus;
|
||||
}
|
||||
|
||||
public function GetDkimValue() : string
|
||||
{
|
||||
return $this->sDkimValue;
|
||||
}
|
||||
|
||||
public function GetAccountName() : string
|
||||
{
|
||||
return \MailSo\Base\Utils::GetAccountNameFromEmail($this->GetEmail(false));
|
||||
|
@ -185,17 +173,9 @@ class Email implements \JsonSerializable
|
|||
return \MailSo\Base\Utils::GetDomainFromEmail($this->GetEmail($bIdn));
|
||||
}
|
||||
|
||||
public function SetDkimStatusAndValue(string $sDkimStatus, string $sDkimValue = '')
|
||||
public function SetDkimStatus(string $sDkimStatus)
|
||||
{
|
||||
$this->sDkimStatus = Enumerations\DkimStatus::normalizeValue($sDkimStatus);
|
||||
$this->sDkimValue = $sDkimValue;
|
||||
}
|
||||
|
||||
public function ToArray(bool $bIdn = false, bool $bDkim = true) : array
|
||||
{
|
||||
return $bDkim ?
|
||||
array($this->sDisplayName, $this->GetEmail($bIdn), $this->sDkimStatus, $this->sDkimValue) :
|
||||
array($this->sDisplayName, $this->GetEmail($bIdn));
|
||||
}
|
||||
|
||||
public function ToString(bool $bConvertSpecialsName = false, bool $bIdn = false) : string
|
||||
|
@ -227,8 +207,7 @@ class Email implements \JsonSerializable
|
|||
'@Object' => 'Object/Email',
|
||||
'Name' => \MailSo\Base\Utils::Utf8Clear($this->GetDisplayName()),
|
||||
'Email' => \MailSo\Base\Utils::Utf8Clear($this->GetEmail(true)),
|
||||
'DkimStatus' => $this->GetDkimStatus(),
|
||||
'DkimValue' => $this->GetDkimValue()
|
||||
'DkimStatus' => $this->sDkimStatus
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,16 +33,6 @@ class EmailCollection extends \MailSo\Base\Collection
|
|||
parent::append($oEmail, $bToTop);
|
||||
}
|
||||
|
||||
public function ToArray() : array
|
||||
{
|
||||
$aReturn = array();
|
||||
foreach ($this as $oEmail) {
|
||||
$aReturn[] = $oEmail->ToArray();
|
||||
}
|
||||
|
||||
return $aReturn;
|
||||
}
|
||||
|
||||
public function MergeWithOtherCollection(EmailCollection $oEmails) : self
|
||||
{
|
||||
foreach ($oEmails as $oEmail) {
|
||||
|
|
|
@ -196,60 +196,46 @@ class HeaderCollection extends \MailSo\Base\Collection
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function DkimStatuses() : array
|
||||
/**
|
||||
* https://www.rfc-editor.org/rfc/rfc8601
|
||||
* dkim=pass header.d=domain.tld header.s=s1 header.b=F2SfoZWw;
|
||||
* spf=pass (ORIGINATING: domain of "snappymail@domain.tld" designates 0.0.0.0 as permitted sender) smtp.mailfrom="snappymail@domain.tld";
|
||||
* dmarc=fail reason="SPF not aligned (relaxed), DKIM not aligned (relaxed)" header.from=domain.tld (policy=none)
|
||||
*/
|
||||
public function AuthStatuses() : array
|
||||
{
|
||||
$aResult = array();
|
||||
|
||||
$aResult = [
|
||||
'dkim' => [],
|
||||
'dmarc' => [],
|
||||
'spf' => []
|
||||
];
|
||||
$aHeaders = $this->ValuesByName(Enumerations\Header::AUTHENTICATION_RESULTS);
|
||||
if (\count($aHeaders)) {
|
||||
foreach ($aHeaders as $sHeaderValue) {
|
||||
$sStatus = '';
|
||||
$sHeader = '';
|
||||
$sDkimLine = '';
|
||||
|
||||
$aMatch = array();
|
||||
|
||||
$sHeaderValue = \preg_replace('/[\r\n\t\s]+/', ' ', $sHeaderValue);
|
||||
|
||||
if (\preg_match('/dkim=.+/i', $sHeaderValue, $aMatch) && !empty($aMatch[0])) {
|
||||
$sDkimLine = $aMatch[0];
|
||||
|
||||
$aMatch = array();
|
||||
if (\preg_match('/dkim=([a-zA-Z0-9]+)/i', $sDkimLine, $aMatch) && !empty($aMatch[1])) {
|
||||
$sStatus = $aMatch[1];
|
||||
}
|
||||
|
||||
$aMatch = array();
|
||||
if (\preg_match('/header\.(d|i|from)=([^\s;]+)/i', $sDkimLine, $aMatch) && !empty($aMatch[2])) {
|
||||
$sHeader = \trim($aMatch[2]);
|
||||
}
|
||||
|
||||
if (!empty($sStatus) && !empty($sHeader)) {
|
||||
$aResult[] = array($sStatus, $sHeader, $sDkimLine);
|
||||
}
|
||||
$aHeaders = \implode(';', $aHeaders);
|
||||
$aHeaders = \preg_replace('/[\\r\\n\\t\\s]+/', ' ', $aHeaders);
|
||||
$aHeaders = \explode(';', $aHeaders);
|
||||
foreach ($aHeaders as $sLine) {
|
||||
$aStatus = array();
|
||||
$aHeader = array();
|
||||
if (\preg_match("/(dkim|dmarc|spf)=([a-z0-9]+).*?(;|$)/Di", $sLine, $aStatus)
|
||||
&& \preg_match('/(?:header\\.(?:d|i|from)|smtp.mailfrom)="?([^\\s;"]+)/i', $sLine, $aHeader)
|
||||
) {
|
||||
$sType = \strtolower($aStatus[1]);
|
||||
$aResult[$sType][] = array(\strtolower($aStatus[2]), $aHeader[1], \trim($sLine));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
if (!\count($aResult['dkim'])) {
|
||||
// X-DKIM-Authentication-Results: signer="hostinger.com" status="pass"
|
||||
$aHeaders = $this->ValuesByName(Enumerations\Header::X_DKIM_AUTHENTICATION_RESULTS);
|
||||
foreach ($aHeaders as $sHeaderValue) {
|
||||
$sStatus = '';
|
||||
$sHeader = '';
|
||||
|
||||
$aMatch = array();
|
||||
|
||||
$sHeaderValue = \preg_replace('/[\r\n\t\s]+/', ' ', $sHeaderValue);
|
||||
|
||||
if (\preg_match('/status[\s]?=[\s]?"([a-zA-Z0-9]+)"/i', $sHeaderValue, $aMatch) && !empty($aMatch[1])) {
|
||||
$sStatus = $aMatch[1];
|
||||
}
|
||||
|
||||
if (\preg_match('/signer[\s]?=[\s]?"([^";]+)"/i', $sHeaderValue, $aMatch) && !empty($aMatch[1])) {
|
||||
$sHeader = \trim($aMatch[1]);
|
||||
}
|
||||
|
||||
if (!empty($sStatus) && !empty($sHeader)) {
|
||||
$aResult[] = array($sStatus, $sHeader, $sHeaderValue);
|
||||
$aStatus = array();
|
||||
$aHeader = array();
|
||||
$sHeaderValue = \preg_replace('/[\\r\\n\\t\\s]+/', ' ', $sHeaderValue);
|
||||
if (\preg_match('/status[\\s]?=[\\s]?"([a-zA-Z0-9]+)"/i', $sHeaderValue, $aStatus) && !empty($aStatus[1])
|
||||
&& \preg_match('/signer[\\s]?=[\\s]?"([^";]+)"/i', $sHeaderValue, $aHeader) && !empty($aHeader[1])
|
||||
) {
|
||||
$aResult['dkim'][] = array($aStatus[1], \trim($aHeader[1]), $sHeaderValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -257,19 +243,6 @@ class HeaderCollection extends \MailSo\Base\Collection
|
|||
return $aResult;
|
||||
}
|
||||
|
||||
public function PopulateEmailColectionByDkim(EmailCollection $oEmails) : void
|
||||
{
|
||||
$aDkimStatuses = $this->DkimStatuses();
|
||||
foreach ($oEmails as $oEmail) {
|
||||
$sEmail = $oEmail->GetEmail();
|
||||
foreach ($aDkimStatuses as $aDkimData) {
|
||||
if (isset($aDkimData[0], $aDkimData[1]) && $aDkimData[1] === \strstr($sEmail, $aDkimData[1])) {
|
||||
$oEmail->SetDkimStatusAndValue($aDkimData[0], empty($aDkimData[2]) ? '' : $aDkimData[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function __toString() : string
|
||||
{
|
||||
return \implode("\r\n", $this->getArrayCopy());
|
||||
|
|
Loading…
Add table
Reference in a new issue