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

0001 /*
0002    SPDX-FileCopyrightText: 2018-2024 Laurent Montel <montel@kde.org>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "dkimauthenticationstatusinfo.h"
0008 #include "dkimauthenticationstatusinfoutil.h"
0009 #include "messageviewer_dkimcheckerdebug.h"
0010 
0011 #include <QRegularExpressionMatch>
0012 using namespace MessageViewer;
0013 // see https://tools.ietf.org/html/rfc7601
0014 DKIMAuthenticationStatusInfo::DKIMAuthenticationStatusInfo() = default;
0015 
0016 bool DKIMAuthenticationStatusInfo::parseAuthenticationStatus(const QString &key, bool relaxingParsing)
0017 {
0018     QString valueKey = key;
0019     // kmime remove extra \r\n but we need it for regexp at the end.
0020     if (!valueKey.endsWith(QLatin1StringView("\r\n"))) {
0021         valueKey += QLatin1StringView("\r\n");
0022     }
0023     // https://tools.ietf.org/html/rfc7601#section-2.2
0024     // authres-header = "Authentication-Results:" [CFWS] authserv-id
0025     //                                            [ CFWS authres-version ]
0026     //                                            ( no-result / 1*resinfo ) [CFWS] CRLF
0027 
0028     // 1) extract AuthservId and AuthVersion
0029     QRegularExpressionMatch match;
0030     const QString regStr = DKIMAuthenticationStatusInfoUtil::value_cp() + QLatin1StringView("(?:") + DKIMAuthenticationStatusInfoUtil::cfws_p()
0031         + QLatin1StringView("([0-9]+)") + DKIMAuthenticationStatusInfoUtil::cfws_op() + QLatin1StringView(" )?");
0032     // qDebug() << " regStr" << regStr;
0033     static const QRegularExpression regular1(regStr);
0034     int index = valueKey.indexOf(regular1, 0, &match);
0035     if (index != -1) {
0036         mAuthservId = match.captured(1);
0037         const QString authVersionStr = match.captured(2);
0038         if (!authVersionStr.isEmpty()) {
0039             mAuthVersion = authVersionStr.toInt();
0040         } else {
0041             mAuthVersion = 1;
0042         }
0043         valueKey = valueKey.right(valueKey.length() - (index + match.capturedLength(0)));
0044         // qDebug() << " match.captured(0)"<<match.captured(0)<<"match.captured(1)" <<match.captured(1) << authVersionStr;
0045         // qDebug() << " valueKey" << valueKey;
0046     } else {
0047         return false;
0048     }
0049     // check if message authentication was performed
0050     const QString authResultStr = DKIMAuthenticationStatusInfoUtil::regexMatchO(DKIMAuthenticationStatusInfoUtil::value_cp() + QLatin1StringView(";")
0051                                                                                 + DKIMAuthenticationStatusInfoUtil::cfws_op() + QLatin1StringView("?none"));
0052     // qDebug() << "authResultStr "<<authResultStr;
0053     static const QRegularExpression regular2(authResultStr);
0054     index = valueKey.indexOf(regular2, 0, &match);
0055     if (index != -1) {
0056         // no result
0057         return false;
0058     }
0059     while (!valueKey.isEmpty()) {
0060         // qDebug() << "valueKey LOOP" << valueKey;
0061         const AuthStatusInfo resultInfo = parseAuthResultInfo(valueKey, relaxingParsing);
0062         if (resultInfo.isValid()) {
0063             mListAuthStatusInfo.append(resultInfo);
0064         }
0065     }
0066     return true;
0067 }
0068 
0069 DKIMAuthenticationStatusInfo::AuthStatusInfo DKIMAuthenticationStatusInfo::parseAuthResultInfo(QString &valueKey, bool relaxingParsing)
0070 {
0071     DKIMAuthenticationStatusInfo::AuthStatusInfo authStatusInfo;
0072     // 2) extract methodspec
0073     const QString methodVersionp =
0074         DKIMAuthenticationStatusInfoUtil::cfws_op() + QLatin1Char('/') + DKIMAuthenticationStatusInfoUtil::cfws_op() + QLatin1StringView("([0-9]+)");
0075     const QString method_p =
0076         QLatin1Char('(') + DKIMAuthenticationStatusInfoUtil::keyword_p() + QLatin1StringView(")(?:") + methodVersionp + QLatin1StringView(")?");
0077     QString Keyword_result_p = QStringLiteral("none|pass|fail|softfail|policy|neutral|temperror|permerror");
0078     // older SPF specs (e.g. RFC 4408) use mixed case
0079     Keyword_result_p += QLatin1StringView("|None|Pass|Fail|SoftFail|Neutral|TempError|PermError");
0080     const QString result_p = QLatin1Char('=') + DKIMAuthenticationStatusInfoUtil::cfws_op() + QLatin1Char('(') + Keyword_result_p + QLatin1Char(')');
0081     const QString methodspec_p =
0082         QLatin1Char(';') + DKIMAuthenticationStatusInfoUtil::cfws_op() + method_p + DKIMAuthenticationStatusInfoUtil::cfws_op() + result_p;
0083 
0084     // qDebug() << "methodspec_p " << methodspec_p;
0085     QRegularExpressionMatch match;
0086     static const QRegularExpression reg2(methodspec_p);
0087     int index = valueKey.indexOf(reg2, 0, &match);
0088     if (index == -1) {
0089         valueKey = QString(); // remove it !
0090         qCDebug(MESSAGEVIEWER_DKIMCHECKER_LOG) << "methodspec not found ";
0091         // no result
0092         return authStatusInfo;
0093     }
0094     // qDebug() << " match" << match.captured(0) << match.captured(1) << match.capturedTexts();
0095     authStatusInfo.method = match.captured(1);
0096     const QString authVersionStr = match.captured(2);
0097     if (!authVersionStr.isEmpty()) {
0098         authStatusInfo.methodVersion = authVersionStr.toInt();
0099     } else {
0100         authStatusInfo.methodVersion = 1;
0101     }
0102     authStatusInfo.result = match.captured(3);
0103 
0104     valueKey = valueKey.right(valueKey.length() - (index + match.capturedLength(0))); // Improve it!
0105 
0106     // 3) extract reasonspec (optional)
0107     const QString reasonspec_p =
0108         DKIMAuthenticationStatusInfoUtil::regexMatchO(QLatin1StringView("reason") + DKIMAuthenticationStatusInfoUtil::cfws_op() + QLatin1Char('=')
0109                                                       + DKIMAuthenticationStatusInfoUtil::cfws_op() + DKIMAuthenticationStatusInfoUtil::value_cp());
0110     static const QRegularExpression reg31(reasonspec_p);
0111     index = valueKey.indexOf(reg31, 0, &match);
0112     if (index != -1) {
0113         // qDebug() << " reason " << match.capturedTexts();
0114         authStatusInfo.reason = match.captured(2);
0115         valueKey = valueKey.right(valueKey.length() - (index + match.capturedLength(0))); // Improve it!
0116     }
0117     // 4) extract propspec (optional)
0118     QString pvalue_p = DKIMAuthenticationStatusInfoUtil::value_p() + QLatin1StringView("|(?:(?:") + DKIMAuthenticationStatusInfoUtil::localPart_p()
0119         + QLatin1StringView("?@)?") + DKIMAuthenticationStatusInfoUtil::domainName_p() + QLatin1Char(')');
0120     if (relaxingParsing) {
0121         // Allow "/" in the header.b (or other) property, even if it is not in a quoted-string
0122         pvalue_p += QStringLiteral("|[^ \\x00-\\x1F\\x7F()<>@,;:\\\\\"[\\]?=]+");
0123     }
0124 
0125     const QString property_p = QLatin1StringView("mailfrom|rcptto") + QLatin1Char('|') + DKIMAuthenticationStatusInfoUtil::keyword_p();
0126     const QString propspec_p = QLatin1Char('(') + DKIMAuthenticationStatusInfoUtil::keyword_p() + QLatin1Char(')') + DKIMAuthenticationStatusInfoUtil::cfws_op()
0127         + QLatin1StringView("\\.") + DKIMAuthenticationStatusInfoUtil::cfws_op() + QLatin1Char('(') + property_p + QLatin1Char(')')
0128         + DKIMAuthenticationStatusInfoUtil::cfws_op() + QLatin1Char('=') + DKIMAuthenticationStatusInfoUtil::cfws_op() + QLatin1Char('(')
0129         + pvalue_p /*+ QLatin1Char(')')*/;
0130 
0131     // qDebug() << "propspec_p " << propspec_p;
0132 
0133     const QString regexp = DKIMAuthenticationStatusInfoUtil::regexMatchO(propspec_p);
0134     static const QRegularExpression reg(regexp);
0135     if (!reg.isValid()) {
0136         qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << " reg error : " << reg.errorString();
0137     } else {
0138         index = valueKey.indexOf(reg, 0, &match);
0139         while (index != -1) {
0140             // qDebug() << " propspec " << match.capturedTexts();
0141             valueKey = valueKey.right(valueKey.length() - (index + match.capturedLength(0))); // Improve it!
0142             // qDebug() << " value KEy " << valueKey;
0143             const QString &captured1 = match.captured(1);
0144             // qDebug() << " captured1 " << captured1;
0145             if (captured1 == QLatin1StringView("header")) {
0146                 AuthStatusInfo::Property prop;
0147                 prop.type = match.captured(2);
0148                 prop.value = match.captured(3);
0149                 authStatusInfo.header.append(prop);
0150             } else if (captured1 == QLatin1StringView("smtp")) {
0151                 AuthStatusInfo::Property prop;
0152                 prop.type = match.captured(2);
0153                 prop.value = match.captured(3);
0154                 authStatusInfo.smtp.append(prop);
0155             } else if (captured1 == QLatin1StringView("body")) {
0156                 AuthStatusInfo::Property prop;
0157                 prop.type = match.captured(2);
0158                 prop.value = match.captured(3);
0159                 authStatusInfo.body.append(prop);
0160             } else if (captured1 == QLatin1StringView("policy")) {
0161                 AuthStatusInfo::Property prop;
0162                 prop.type = match.captured(2);
0163                 prop.value = match.captured(3);
0164                 authStatusInfo.policy.append(prop);
0165             } else {
0166                 qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "Unknown type found " << captured1;
0167             }
0168             index = valueKey.indexOf(reg, 0, &match);
0169         }
0170     }
0171     return authStatusInfo;
0172 }
0173 
0174 int DKIMAuthenticationStatusInfo::authVersion() const
0175 {
0176     return mAuthVersion;
0177 }
0178 
0179 void DKIMAuthenticationStatusInfo::setAuthVersion(int authVersion)
0180 {
0181     mAuthVersion = authVersion;
0182 }
0183 
0184 QString DKIMAuthenticationStatusInfo::reasonSpec() const
0185 {
0186     return mReasonSpec;
0187 }
0188 
0189 void DKIMAuthenticationStatusInfo::setReasonSpec(const QString &reasonSpec)
0190 {
0191     mReasonSpec = reasonSpec;
0192 }
0193 
0194 bool DKIMAuthenticationStatusInfo::operator==(const DKIMAuthenticationStatusInfo &other) const
0195 {
0196     return mAuthservId == other.authservId() && mAuthVersion == other.authVersion() && mReasonSpec == other.reasonSpec()
0197         && mListAuthStatusInfo == other.listAuthStatusInfo();
0198 }
0199 
0200 QList<DKIMAuthenticationStatusInfo::AuthStatusInfo> DKIMAuthenticationStatusInfo::listAuthStatusInfo() const
0201 {
0202     return mListAuthStatusInfo;
0203 }
0204 
0205 void DKIMAuthenticationStatusInfo::setListAuthStatusInfo(const QList<AuthStatusInfo> &listAuthStatusInfo)
0206 {
0207     mListAuthStatusInfo = listAuthStatusInfo;
0208 }
0209 
0210 QString DKIMAuthenticationStatusInfo::authservId() const
0211 {
0212     return mAuthservId;
0213 }
0214 
0215 void DKIMAuthenticationStatusInfo::setAuthservId(const QString &authservId)
0216 {
0217     mAuthservId = authservId;
0218 }
0219 
0220 QDebug operator<<(QDebug d, const DKIMAuthenticationStatusInfo &t)
0221 {
0222     d << "mAuthservId: " << t.authservId();
0223     d << "mReasonSpec: " << t.reasonSpec();
0224     d << "mAuthVersion: " << t.authVersion() << '\n';
0225     const auto listAuthStatusInfo = t.listAuthStatusInfo();
0226     for (const DKIMAuthenticationStatusInfo::AuthStatusInfo &info : listAuthStatusInfo) {
0227         d << "mListAuthStatusInfo: " << info.method << " : " << info.result << " : " << info.methodVersion << " : " << info.reason << '\n';
0228         d << "Property:" << '\n';
0229         if (!info.smtp.isEmpty()) {
0230             for (const DKIMAuthenticationStatusInfo::AuthStatusInfo::Property &prop : info.smtp) {
0231                 d << "    smtp " << prop.type << " : " << prop.value << '\n';
0232             }
0233         }
0234         if (!info.header.isEmpty()) {
0235             for (const DKIMAuthenticationStatusInfo::AuthStatusInfo::Property &prop : info.header) {
0236                 d << "    header " << prop.type << " : " << prop.value << '\n';
0237             }
0238         }
0239         if (!info.body.isEmpty()) {
0240             for (const DKIMAuthenticationStatusInfo::AuthStatusInfo::Property &prop : info.body) {
0241                 d << "    body " << prop.type << " : " << prop.value << '\n';
0242             }
0243         }
0244         if (!info.policy.isEmpty()) {
0245             for (const DKIMAuthenticationStatusInfo::AuthStatusInfo::Property &prop : info.policy) {
0246                 d << "    policy " << prop.type << " : " << prop.value << '\n';
0247             }
0248         }
0249     }
0250     return d;
0251 }
0252 
0253 bool DKIMAuthenticationStatusInfo::AuthStatusInfo::operator==(const DKIMAuthenticationStatusInfo::AuthStatusInfo &other) const
0254 {
0255     return other.method == method && other.result == result && other.methodVersion == methodVersion && other.reason == reason && other.policy == policy
0256         && other.smtp == smtp && other.header == header && other.body == body;
0257 }
0258 
0259 bool DKIMAuthenticationStatusInfo::AuthStatusInfo::isValid() const
0260 {
0261     return !method.isEmpty();
0262 }