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 }