File indexing completed on 2025-03-09 04:54:31

0001 /*
0002    SPDX-FileCopyrightText: 2019-2024 Laurent Montel <montel@kde.org>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "dmarcpolicyjob.h"
0008 #include "dkimutil.h"
0009 #include "dmarcrecordjob.h"
0010 #include "messageviewer_dkimcheckerdebug.h"
0011 using namespace MessageViewer;
0012 
0013 DMARCPolicyJob::DMARCPolicyJob(QObject *parent)
0014     : QObject(parent)
0015 {
0016 }
0017 
0018 DMARCPolicyJob::~DMARCPolicyJob() = default;
0019 
0020 bool DMARCPolicyJob::canStart() const
0021 {
0022     return !mEmailAddress.isEmpty();
0023 }
0024 
0025 bool DMARCPolicyJob::start()
0026 {
0027     if (!canStart()) {
0028         qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << " Impossible to start DMARCPolicyJob" << mEmailAddress;
0029         Q_EMIT result({}, mEmailAddress);
0030         deleteLater();
0031         return false;
0032     }
0033 
0034     auto job = new DMARCRecordJob(this);
0035     job->setDomainName(emailDomain());
0036     connect(job, &MessageViewer::DMARCRecordJob::success, this, &DMARCPolicyJob::slotCheckDomain);
0037     connect(job, &MessageViewer::DMARCRecordJob::error, this, [this](const QString &err, const QString &domainName) {
0038         qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "error: " << err << " domain " << domainName;
0039         // Verify subdomain
0040         checkSubDomain(domainName);
0041     });
0042     if (!job->start()) {
0043         Q_EMIT result({}, mEmailAddress);
0044         deleteLater();
0045         return false;
0046     }
0047     return true;
0048 }
0049 
0050 QByteArray DMARCPolicyJob::generateDMARCFromList(const QList<QByteArray> &lst) const
0051 {
0052     QByteArray ba;
0053     if (lst.count() != 1) {
0054         for (const QByteArray &b : lst) {
0055             ba += b;
0056         }
0057         qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "DMARCPolicyJob Key result has more that 1 element" << lst;
0058     } else {
0059         ba = lst.at(0);
0060     }
0061     return ba;
0062 }
0063 
0064 void DMARCPolicyJob::slotCheckSubDomain(const QList<QByteArray> &lst, const QString &domainName)
0065 {
0066     const QByteArray ba = generateDMARCFromList(lst);
0067     DMARCInfo info;
0068     if (info.parseDMARC(QString::fromLocal8Bit(ba))) {
0069         if ((info.version() != QLatin1StringView("DMARC1")) || info.policy().isEmpty() || (info.percentage() > 100 || info.percentage() < 0)) {
0070             Q_EMIT result({}, mEmailAddress);
0071             deleteLater();
0072             return;
0073         } else {
0074             DMARCPolicyJob::DMARCResult val;
0075             val.mAdkim = info.adkim();
0076             val.mPercentage = info.percentage();
0077             val.mPolicy = info.subDomainPolicy().isEmpty() ? info.policy() : info.subDomainPolicy();
0078             // TODO verify it !
0079             val.mDomain = domainName;
0080             val.mSource = domainName;
0081             Q_EMIT result(val, mEmailAddress);
0082             deleteLater();
0083             return;
0084         }
0085     }
0086     Q_EMIT result({}, mEmailAddress);
0087 }
0088 
0089 void DMARCPolicyJob::checkSubDomain(const QString &domainName)
0090 {
0091     const QString subDomain = emailSubDomain(domainName);
0092     if (subDomain != domainName) {
0093         auto job = new DMARCRecordJob(this);
0094         job->setDomainName(subDomain);
0095         connect(job, &MessageViewer::DMARCRecordJob::success, this, &DMARCPolicyJob::slotCheckSubDomain);
0096         connect(job, &MessageViewer::DMARCRecordJob::error, this, [this](const QString &err, const QString &domainName) {
0097             qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "error: " << err << " domain " << domainName;
0098             Q_EMIT result({}, mEmailAddress);
0099             deleteLater();
0100         });
0101         if (!job->start()) {
0102             Q_EMIT result({}, mEmailAddress);
0103             deleteLater();
0104             return;
0105         }
0106     } else {
0107         // Invalid
0108         Q_EMIT result({}, mEmailAddress);
0109         deleteLater();
0110         return;
0111     }
0112 }
0113 
0114 void DMARCPolicyJob::slotCheckDomain(const QList<QByteArray> &lst, const QString &domainName)
0115 {
0116     const QByteArray ba = generateDMARCFromList(lst);
0117     DMARCInfo info;
0118     if (info.parseDMARC(QString::fromLocal8Bit(ba))) {
0119         if ((info.version() != QLatin1StringView("DMARC1")) || info.policy().isEmpty()
0120             || (info.percentage() != -1 && (info.percentage() > 100 || info.percentage() < 0))) {
0121             // Invalid
0122             // Check subdomain
0123             checkSubDomain(domainName);
0124         } else {
0125             DMARCPolicyJob::DMARCResult val;
0126             val.mAdkim = info.adkim();
0127             val.mPercentage = info.percentage();
0128             val.mPolicy = info.policy();
0129             val.mDomain = domainName;
0130             val.mSource = domainName;
0131             Q_EMIT result(val, mEmailAddress);
0132             deleteLater();
0133             return;
0134         }
0135     } else {
0136         // Check subdomain
0137         checkSubDomain(domainName);
0138     }
0139 }
0140 
0141 QString DMARCPolicyJob::emailDomain() const
0142 {
0143     return MessageViewer::DKIMUtil::emailDomain(mEmailAddress);
0144 }
0145 
0146 QString DMARCPolicyJob::emailSubDomain(const QString &domainName) const
0147 {
0148     return MessageViewer::DKIMUtil::emailSubDomain(domainName);
0149 }
0150 
0151 QString DMARCPolicyJob::emailAddress() const
0152 {
0153     return mEmailAddress;
0154 }
0155 
0156 void DMARCPolicyJob::setEmailAddress(const QString &emailAddress)
0157 {
0158     mEmailAddress = emailAddress;
0159 }
0160 
0161 #include "moc_dmarcpolicyjob.cpp"