1 /*
2     SPDX-FileCopyrightText: 2015-2017 Krzysztof Nowicki <krissn@op.pl>
3 
4     SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "ewspoxautodiscoverrequest.h"
8 
9 #include <QTemporaryFile>
10 #include <QXmlStreamReader>
11 #include <QXmlStreamWriter>
12 
13 #include <KIO/TransferJob>
14 
15 #include "ewsclient_debug.h"
16 
17 static const QString poxAdOuReqNsUri = QStringLiteral("http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006");
18 static const QString poxAdRespNsUri = QStringLiteral("http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006");
19 static const QString poxAdOuRespNsUri = QStringLiteral("http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a");
20 
EwsPoxAutodiscoverRequest(const QUrl & url,const QString & email,const QString & userAgent,bool useNTLMv2,QObject * parent)21 EwsPoxAutodiscoverRequest::EwsPoxAutodiscoverRequest(const QUrl &url, const QString &email, const QString &userAgent, bool useNTLMv2, QObject *parent)
22     : EwsJob(parent)
23     , mUrl(url)
24     , mEmail(email)
25     , mUserAgent(userAgent)
26     , mUseNTLMv2(useNTLMv2)
27     , mServerVersion(EwsServerVersion::ewsVersion2007Sp1)
28     , mAction(Settings)
29 {
30 }
31 
~EwsPoxAutodiscoverRequest()32 EwsPoxAutodiscoverRequest::~EwsPoxAutodiscoverRequest()
33 {
34 }
35 
doSend()36 void EwsPoxAutodiscoverRequest::doSend()
37 {
38     const auto jobs{subjobs()};
39     for (KJob *job : jobs) {
40         job->start();
41     }
42 }
43 
prepare(const QString & body)44 void EwsPoxAutodiscoverRequest::prepare(const QString &body)
45 {
46     mBody = body;
47     mLastUrl = mUrl;
48     KIO::TransferJob *job = KIO::http_post(mUrl, body.toUtf8(), KIO::HideProgressInfo);
49     job->addMetaData(QStringLiteral("content-type"), QStringLiteral("text/xml"));
50     job->addMetaData(QStringLiteral("no-auth-prompt"), QStringLiteral("true"));
51     if (mUseNTLMv2) {
52         job->addMetaData(QStringLiteral("EnableNTLMv2Auth"), QStringLiteral("true"));
53     }
54     if (!mUserAgent.isEmpty()) {
55         job->addMetaData(QStringLiteral("UserAgent"), mUserAgent);
56     }
57     // config->readEntry("no-spoof-check", false)
58 
59     connect(job, &KIO::TransferJob::result, this, &EwsPoxAutodiscoverRequest::requestResult);
60     connect(job, &KIO::TransferJob::data, this, &EwsPoxAutodiscoverRequest::requestData);
61     connect(job, &KIO::TransferJob::redirection, this, &EwsPoxAutodiscoverRequest::requestRedirect);
62 
63     addSubjob(job);
64 }
65 
start()66 void EwsPoxAutodiscoverRequest::start()
67 {
68     QString reqString;
69     QXmlStreamWriter writer(&reqString);
70 
71     writer.writeStartDocument();
72 
73     writer.writeDefaultNamespace(poxAdOuReqNsUri);
74 
75     writer.writeStartElement(poxAdOuReqNsUri, QStringLiteral("Autodiscover"));
76 
77     writer.writeStartElement(poxAdOuReqNsUri, QStringLiteral("Request"));
78 
79     writer.writeTextElement(poxAdOuReqNsUri, QStringLiteral("EMailAddress"), mEmail);
80     writer.writeTextElement(poxAdOuReqNsUri, QStringLiteral("AcceptableResponseSchema"), poxAdOuRespNsUri);
81 
82     writer.writeEndElement(); // Request
83 
84     writer.writeEndElement(); // Autodiscover
85 
86     writer.writeEndDocument();
87 
88     qCDebug(EWSCLI_PROTO_LOG) << reqString;
89 
90     qCDebugNC(EWSCLI_REQUEST_LOG) << QStringLiteral("Starting POX Autodiscovery request (url: ") << mUrl << QStringLiteral(", email: ") << mEmail;
91     prepare(reqString);
92 
93     doSend();
94 }
95 
requestData(KIO::Job * job,const QByteArray & data)96 void EwsPoxAutodiscoverRequest::requestData(KIO::Job *job, const QByteArray &data)
97 {
98     Q_UNUSED(job)
99 
100     qCDebug(EWSCLI_PROTO_LOG) << "data" << job << data;
101     mResponseData += QString::fromUtf8(data);
102 }
103 
requestResult(KJob * job)104 void EwsPoxAutodiscoverRequest::requestResult(KJob *job)
105 {
106     if (EWSCLI_PROTO_LOG().isDebugEnabled()) {
107         ewsLogDir.setAutoRemove(false);
108         if (ewsLogDir.isValid()) {
109             QTemporaryFile dumpFile(ewsLogDir.path() + QStringLiteral("/ews_xmldump_XXXXXXX.xml"));
110             dumpFile.open();
111             dumpFile.setAutoRemove(false);
112             dumpFile.write(mResponseData.toUtf8());
113             qCDebug(EWSCLI_PROTO_LOG) << "response dumped to" << dumpFile.fileName();
114             dumpFile.close();
115         }
116     }
117 
118     auto trJob = qobject_cast<KIO::TransferJob *>(job);
119     int resp = trJob->metaData()[QStringLiteral("responsecode")].toUInt();
120 
121     if (job->error() != 0) {
122         setErrorMsg(QStringLiteral("Failed to process EWS request: ") + job->errorString());
123         setError(job->error());
124     } else if (resp >= 300) {
125         setErrorMsg(QStringLiteral("Failed to process EWS request - HTTP code %1").arg(resp));
126         setError(resp);
127     } else {
128         QXmlStreamReader reader(mResponseData);
129         readResponse(reader);
130     }
131 
132     emitResult();
133 }
134 
readResponse(QXmlStreamReader & reader)135 bool EwsPoxAutodiscoverRequest::readResponse(QXmlStreamReader &reader)
136 {
137     if (!reader.readNextStartElement()) {
138         return setErrorMsg(QStringLiteral("Failed to read POX response XML"));
139     }
140 
141     if ((reader.name() != QLatin1String("Autodiscover")) || (reader.namespaceUri() != poxAdRespNsUri)) {
142         return setErrorMsg(QStringLiteral("Failed to read POX response - not an Autodiscover response"));
143     }
144 
145     if (!reader.readNextStartElement()) {
146         return setErrorMsg(QStringLiteral("Failed to read POX response - expected %1 element").arg(QStringLiteral("Response")));
147     }
148 
149     if ((reader.name() != QLatin1String("Response")) || (reader.namespaceUri() != poxAdOuRespNsUri)) {
150         return setErrorMsg(
151             QStringLiteral("Failed to read POX response - expected %1 element, found %2").arg(QStringLiteral("Response").arg(reader.name().toString())));
152     }
153 
154     while (reader.readNextStartElement()) {
155         if (reader.namespaceUri() != poxAdOuRespNsUri) {
156             return setErrorMsg(QStringLiteral("Failed to read POX response - invalid namespace"));
157         }
158 
159         if (reader.name() == QLatin1String("User")) {
160             reader.skipCurrentElement();
161         } else if (reader.name() == QLatin1String("Account")) {
162             if (!readAccount(reader)) {
163                 return false;
164             }
165         } else {
166             return setErrorMsg(
167                 QStringLiteral("Failed to read POX response - unknown element '%1' inside '%2'").arg(reader.name().toString(), QStringLiteral("Response")));
168         }
169     }
170     return true;
171 }
172 
readAccount(QXmlStreamReader & reader)173 bool EwsPoxAutodiscoverRequest::readAccount(QXmlStreamReader &reader)
174 {
175     while (reader.readNextStartElement()) {
176         if (reader.namespaceUri() != poxAdOuRespNsUri) {
177             return setErrorMsg(QStringLiteral("Failed to read POX response - invalid namespace"));
178         }
179         const QStringRef readerName = reader.name();
180         if (readerName == QLatin1String("Action")) {
181             QString action = reader.readElementText();
182             if (action == QLatin1String("settings")) {
183                 mAction = Settings;
184             } else if (action == QLatin1String("redirectUrl")) {
185                 mAction = RedirectUrl;
186             } else if (action == QLatin1String("redirectAddr")) {
187                 mAction = RedirectAddr;
188             } else {
189                 return setErrorMsg(QStringLiteral("Failed to read POX response - unknown action '%1'").arg(action));
190             }
191         } else if (readerName == QLatin1String("RedirectUrl")) {
192             mRedirectUrl = reader.readElementText();
193         } else if (readerName == QLatin1String("RedirectAddr")) {
194             mRedirectAddr = reader.readElementText();
195         } else if (readerName == QLatin1String("RedirectAddr")) {
196             mRedirectAddr = reader.readElementText();
197         } else if (readerName == QLatin1String("Protocol")) {
198             if (!readProtocol(reader)) {
199                 return false;
200             }
201         } else {
202             reader.skipCurrentElement();
203         }
204     }
205     return true;
206 }
207 
readProtocol(QXmlStreamReader & reader)208 bool EwsPoxAutodiscoverRequest::readProtocol(QXmlStreamReader &reader)
209 {
210     Protocol proto;
211 
212     while (reader.readNextStartElement()) {
213         if (reader.namespaceUri() != poxAdOuRespNsUri) {
214             return setErrorMsg(QStringLiteral("Failed to read POX response - invalid namespace"));
215         }
216 
217         const QStringRef readerName = reader.name();
218         if (readerName == QLatin1String("Type")) {
219             QString type = reader.readElementText();
220             if (type == QLatin1String("EXCH")) {
221                 proto.mType = ExchangeProto;
222             } else if (type == QLatin1String("EXPR")) {
223                 proto.mType = ExchangeProxyProto;
224             } else if (type == QLatin1String("WEB")) {
225                 proto.mType = ExchangeWebProto;
226             } else {
227                 return setErrorMsg(QStringLiteral("Failed to read POX response - unknown protocol '%1'").arg(type));
228             }
229         } else if (readerName == QLatin1String("EwsUrl")) {
230             proto.mEwsUrl = reader.readElementText();
231         } else if (readerName == QLatin1String("OabUrl")) {
232             proto.mOabUrl = reader.readElementText();
233         } else {
234             reader.skipCurrentElement();
235         }
236     }
237 
238     qCDebug(EWSCLI_LOG) << "Adding proto type" << proto.mType << proto.isValid();
239     mProtocols[proto.mType] = proto;
240 
241     return true;
242 }
243 
requestRedirect(KIO::Job * job,const QUrl & url)244 void EwsPoxAutodiscoverRequest::requestRedirect(KIO::Job *job, const QUrl &url)
245 {
246     Q_UNUSED(job)
247 
248     qCDebugNC(EWSCLI_REQUEST_LOG) << QStringLiteral("Got HTTP redirect to: ") << mUrl;
249 
250     mLastUrl = url;
251 }
252 
dump() const253 void EwsPoxAutodiscoverRequest::dump() const
254 {
255     ewsLogDir.setAutoRemove(false);
256     if (ewsLogDir.isValid()) {
257         QTemporaryFile reqDumpFile(ewsLogDir.path() + QStringLiteral("/ews_xmlreqdump_XXXXXXX.xml"));
258         reqDumpFile.open();
259         reqDumpFile.setAutoRemove(false);
260         reqDumpFile.write(mBody.toUtf8());
261         reqDumpFile.close();
262         QTemporaryFile resDumpFile(ewsLogDir.path() + QStringLiteral("/ews_xmlresdump_XXXXXXX.xml"));
263         resDumpFile.open();
264         resDumpFile.setAutoRemove(false);
265         resDumpFile.write(mResponseData.toUtf8());
266         resDumpFile.close();
267         qCDebug(EWSCLI_LOG) << "request  dumped to" << reqDumpFile.fileName();
268         qCDebug(EWSCLI_LOG) << "response dumped to" << resDumpFile.fileName();
269     } else {
270         qCWarning(EWSCLI_LOG) << "failed to dump request and response";
271     }
272 }
273