File indexing completed on 2025-01-05 04:50:03

0001 /*
0002     SPDX-FileCopyrightText: 2015-2017 Krzysztof Nowicki <krissn@op.pl>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "ewspoxautodiscoverrequest.h"
0008 
0009 #include <QTemporaryFile>
0010 #include <QXmlStreamReader>
0011 #include <QXmlStreamWriter>
0012 
0013 #include <KIO/TransferJob>
0014 
0015 #include "ewsclient_debug.h"
0016 
0017 static const QString poxAdOuReqNsUri = QStringLiteral("http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006");
0018 static const QString poxAdRespNsUri = QStringLiteral("http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006");
0019 static const QString poxAdOuRespNsUri = QStringLiteral("http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a");
0020 
0021 EwsPoxAutodiscoverRequest::EwsPoxAutodiscoverRequest(const QUrl &url, const QString &email, const QString &userAgent, bool useNTLMv2, QObject *parent)
0022     : EwsJob(parent)
0023     , mUrl(url)
0024     , mEmail(email)
0025     , mUserAgent(userAgent)
0026     , mUseNTLMv2(useNTLMv2)
0027     , mServerVersion(EwsServerVersion::ewsVersion2007Sp1)
0028     , mAction(Settings)
0029 {
0030 }
0031 
0032 EwsPoxAutodiscoverRequest::~EwsPoxAutodiscoverRequest() = default;
0033 
0034 void EwsPoxAutodiscoverRequest::doSend()
0035 {
0036     const auto jobs{subjobs()};
0037     for (KJob *job : jobs) {
0038         job->start();
0039     }
0040 }
0041 
0042 void EwsPoxAutodiscoverRequest::prepare(const QString &body)
0043 {
0044     mBody = body;
0045     mLastUrl = mUrl;
0046     KIO::TransferJob *job = KIO::http_post(mUrl, body.toUtf8(), KIO::HideProgressInfo);
0047     job->addMetaData(QStringLiteral("content-type"), QStringLiteral("text/xml"));
0048     job->addMetaData(QStringLiteral("no-auth-prompt"), QStringLiteral("true"));
0049     if (mUseNTLMv2) {
0050         job->addMetaData(QStringLiteral("EnableNTLMv2Auth"), QStringLiteral("true"));
0051     }
0052     if (!mUserAgent.isEmpty()) {
0053         job->addMetaData(QStringLiteral("UserAgent"), mUserAgent);
0054     }
0055     // config->readEntry("no-spoof-check", false)
0056 
0057     connect(job, &KIO::TransferJob::result, this, &EwsPoxAutodiscoverRequest::requestResult);
0058     connect(job, &KIO::TransferJob::data, this, &EwsPoxAutodiscoverRequest::requestData);
0059     connect(job, &KIO::TransferJob::redirection, this, &EwsPoxAutodiscoverRequest::requestRedirect);
0060 
0061     addSubjob(job);
0062 }
0063 
0064 void EwsPoxAutodiscoverRequest::start()
0065 {
0066     QString reqString;
0067     QXmlStreamWriter writer(&reqString);
0068 
0069     writer.writeStartDocument();
0070 
0071     writer.writeDefaultNamespace(poxAdOuReqNsUri);
0072 
0073     writer.writeStartElement(poxAdOuReqNsUri, QStringLiteral("Autodiscover"));
0074 
0075     writer.writeStartElement(poxAdOuReqNsUri, QStringLiteral("Request"));
0076 
0077     writer.writeTextElement(poxAdOuReqNsUri, QStringLiteral("EMailAddress"), mEmail);
0078     writer.writeTextElement(poxAdOuReqNsUri, QStringLiteral("AcceptableResponseSchema"), poxAdOuRespNsUri);
0079 
0080     writer.writeEndElement(); // Request
0081 
0082     writer.writeEndElement(); // Autodiscover
0083 
0084     writer.writeEndDocument();
0085 
0086     qCDebug(EWSCLI_PROTO_LOG) << reqString;
0087 
0088     qCDebugNC(EWSCLI_REQUEST_LOG) << QStringLiteral("Starting POX Autodiscovery request (url: ") << mUrl << QStringLiteral(", email: ") << mEmail;
0089     prepare(reqString);
0090 
0091     doSend();
0092 }
0093 
0094 void EwsPoxAutodiscoverRequest::requestData(KIO::Job *job, const QByteArray &data)
0095 {
0096     Q_UNUSED(job)
0097 
0098     qCDebug(EWSCLI_PROTO_LOG) << "data" << job << data;
0099     mResponseData += QString::fromUtf8(data);
0100 }
0101 
0102 void EwsPoxAutodiscoverRequest::requestResult(KJob *job)
0103 {
0104     if (EWSCLI_PROTO_LOG().isDebugEnabled()) {
0105         ewsLogDir.setAutoRemove(false);
0106         if (ewsLogDir.isValid()) {
0107             QTemporaryFile dumpFile(ewsLogDir.path() + QStringLiteral("/ews_xmldump_XXXXXXX.xml"));
0108             dumpFile.open();
0109             dumpFile.setAutoRemove(false);
0110             dumpFile.write(mResponseData.toUtf8());
0111             qCDebug(EWSCLI_PROTO_LOG) << "response dumped to" << dumpFile.fileName();
0112             dumpFile.close();
0113         }
0114     }
0115 
0116     auto trJob = qobject_cast<KIO::TransferJob *>(job);
0117     int resp = trJob->metaData()[QStringLiteral("responsecode")].toUInt();
0118 
0119     if (job->error() != 0) {
0120         setErrorMsg(QStringLiteral("Failed to process EWS request: ") + job->errorString());
0121         setError(job->error());
0122     } else if (resp >= 300) {
0123         setErrorMsg(QStringLiteral("Failed to process EWS request - HTTP code %1").arg(resp));
0124         setError(resp);
0125     } else {
0126         QXmlStreamReader reader(mResponseData);
0127         readResponse(reader);
0128     }
0129 
0130     emitResult();
0131 }
0132 
0133 bool EwsPoxAutodiscoverRequest::readResponse(QXmlStreamReader &reader)
0134 {
0135     if (!reader.readNextStartElement()) {
0136         return setErrorMsg(QStringLiteral("Failed to read POX response XML"));
0137     }
0138 
0139     if ((reader.name() != QLatin1StringView("Autodiscover")) || (reader.namespaceUri() != poxAdRespNsUri)) {
0140         return setErrorMsg(QStringLiteral("Failed to read POX response - not an Autodiscover response"));
0141     }
0142 
0143     if (!reader.readNextStartElement()) {
0144         return setErrorMsg(QStringLiteral("Failed to read POX response - expected %1 element").arg(QStringLiteral("Response")));
0145     }
0146 
0147     if ((reader.name() != QLatin1StringView("Response")) || (reader.namespaceUri() != poxAdOuRespNsUri)) {
0148         return setErrorMsg(
0149             QStringLiteral("Failed to read POX response - expected %1 element, found %2").arg(QStringLiteral("Response").arg(reader.name().toString())));
0150     }
0151 
0152     while (reader.readNextStartElement()) {
0153         if (reader.namespaceUri() != poxAdOuRespNsUri) {
0154             return setErrorMsg(QStringLiteral("Failed to read POX response - invalid namespace"));
0155         }
0156 
0157         if (reader.name() == QLatin1StringView("User")) {
0158             reader.skipCurrentElement();
0159         } else if (reader.name() == QLatin1StringView("Account")) {
0160             if (!readAccount(reader)) {
0161                 return false;
0162             }
0163         } else {
0164             return setErrorMsg(
0165                 QStringLiteral("Failed to read POX response - unknown element '%1' inside '%2'").arg(reader.name().toString(), QStringLiteral("Response")));
0166         }
0167     }
0168     return true;
0169 }
0170 
0171 bool EwsPoxAutodiscoverRequest::readAccount(QXmlStreamReader &reader)
0172 {
0173     while (reader.readNextStartElement()) {
0174         if (reader.namespaceUri() != poxAdOuRespNsUri) {
0175             return setErrorMsg(QStringLiteral("Failed to read POX response - invalid namespace"));
0176         }
0177         const QStringView readerName = reader.name();
0178         if (readerName == QLatin1StringView("Action")) {
0179             QString action = reader.readElementText();
0180             if (action == QLatin1StringView("settings")) {
0181                 mAction = Settings;
0182             } else if (action == QLatin1StringView("redirectUrl")) {
0183                 mAction = RedirectUrl;
0184             } else if (action == QLatin1StringView("redirectAddr")) {
0185                 mAction = RedirectAddr;
0186             } else {
0187                 return setErrorMsg(QStringLiteral("Failed to read POX response - unknown action '%1'").arg(action));
0188             }
0189         } else if (readerName == QLatin1StringView("RedirectUrl")) {
0190             mRedirectUrl = reader.readElementText();
0191         } else if (readerName == QLatin1StringView("RedirectAddr")) {
0192             mRedirectAddr = reader.readElementText();
0193         } else if (readerName == QLatin1StringView("RedirectAddr")) {
0194             mRedirectAddr = reader.readElementText();
0195         } else if (readerName == QLatin1StringView("Protocol")) {
0196             if (!readProtocol(reader)) {
0197                 return false;
0198             }
0199         } else {
0200             reader.skipCurrentElement();
0201         }
0202     }
0203     return true;
0204 }
0205 
0206 bool EwsPoxAutodiscoverRequest::readProtocol(QXmlStreamReader &reader)
0207 {
0208     Protocol proto;
0209 
0210     while (reader.readNextStartElement()) {
0211         if (reader.namespaceUri() != poxAdOuRespNsUri) {
0212             return setErrorMsg(QStringLiteral("Failed to read POX response - invalid namespace"));
0213         }
0214         const QStringView readerName = reader.name();
0215         if (readerName == QLatin1StringView("Type")) {
0216             QString type = reader.readElementText();
0217             if (type == QLatin1StringView("EXCH")) {
0218                 proto.mType = ExchangeProto;
0219             } else if (type == QLatin1StringView("EXPR")) {
0220                 proto.mType = ExchangeProxyProto;
0221             } else if (type == QLatin1StringView("WEB")) {
0222                 proto.mType = ExchangeWebProto;
0223             } else {
0224                 return setErrorMsg(QStringLiteral("Failed to read POX response - unknown protocol '%1'").arg(type));
0225             }
0226         } else if (readerName == QLatin1StringView("EwsUrl")) {
0227             proto.mEwsUrl = reader.readElementText();
0228         } else if (readerName == QLatin1StringView("OabUrl")) {
0229             proto.mOabUrl = reader.readElementText();
0230         } else {
0231             reader.skipCurrentElement();
0232         }
0233     }
0234 
0235     qCDebug(EWSCLI_LOG) << "Adding proto type" << proto.mType << proto.isValid();
0236     mProtocols[proto.mType] = proto;
0237 
0238     return true;
0239 }
0240 
0241 void EwsPoxAutodiscoverRequest::requestRedirect(KIO::Job *job, const QUrl &url)
0242 {
0243     Q_UNUSED(job)
0244 
0245     qCDebugNC(EWSCLI_REQUEST_LOG) << QStringLiteral("Got HTTP redirect to: ") << mUrl;
0246 
0247     mLastUrl = url;
0248 }
0249 
0250 void EwsPoxAutodiscoverRequest::dump() const
0251 {
0252     ewsLogDir.setAutoRemove(false);
0253     if (ewsLogDir.isValid()) {
0254         QTemporaryFile reqDumpFile(ewsLogDir.path() + QStringLiteral("/ews_xmlreqdump_XXXXXXX.xml"));
0255         reqDumpFile.open();
0256         reqDumpFile.setAutoRemove(false);
0257         reqDumpFile.write(mBody.toUtf8());
0258         reqDumpFile.close();
0259         QTemporaryFile resDumpFile(ewsLogDir.path() + QStringLiteral("/ews_xmlresdump_XXXXXXX.xml"));
0260         resDumpFile.open();
0261         resDumpFile.setAutoRemove(false);
0262         resDumpFile.write(mResponseData.toUtf8());
0263         resDumpFile.close();
0264         qCDebug(EWSCLI_LOG) << "request  dumped to" << reqDumpFile.fileName();
0265         qCDebug(EWSCLI_LOG) << "response dumped to" << resDumpFile.fileName();
0266     } else {
0267         qCWarning(EWSCLI_LOG) << "failed to dump request and response";
0268     }
0269 }
0270 
0271 #include "moc_ewspoxautodiscoverrequest.cpp"