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