1 /*
2 * Copyright (C) 2008-2021 The QXmpp developers
3 *
4 * Author:
5 * Jeremy Lainé
6 *
7 * Source:
8 * https://github.com/qxmpp-project/qxmpp
9 *
10 * This file is a part of QXmpp library.
11 *
12 * This library is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Lesser General Public
14 * License as published by the Free Software Foundation; either
15 * version 2.1 of the License, or (at your option) any later version.
16 *
17 * This library is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * Lesser General Public License for more details.
21 *
22 */
23
24 #include "QXmppIncomingServer.h"
25
26 #include "QXmppConstants_p.h"
27 #include "QXmppDialback.h"
28 #include "QXmppOutgoingServer.h"
29 #include "QXmppStartTlsPacket.h"
30 #include "QXmppStreamFeatures.h"
31 #include "QXmppUtils.h"
32
33 #include <QDomElement>
34 #include <QHostAddress>
35 #include <QSslKey>
36 #include <QSslSocket>
37
38 class QXmppIncomingServerPrivate
39 {
40 public:
41 QXmppIncomingServerPrivate(QXmppIncomingServer *qq);
42 QString origin() const;
43
44 QSet<QString> authenticated;
45 QString domain;
46 QString localStreamId;
47
48 private:
49 QXmppIncomingServer *q;
50 };
51
QXmppIncomingServerPrivate(QXmppIncomingServer * qq)52 QXmppIncomingServerPrivate::QXmppIncomingServerPrivate(QXmppIncomingServer *qq)
53 : q(qq)
54 {
55 }
56
origin() const57 QString QXmppIncomingServerPrivate::origin() const
58 {
59 QSslSocket *socket = q->socket();
60 if (socket)
61 return socket->peerAddress().toString() + " " + QString::number(socket->peerPort());
62 else
63 return "<unknown>";
64 }
65
66 /// Constructs a new incoming server stream.
67 ///
68 /// \param socket The socket for the XMPP stream.
69 /// \param domain The local domain.
70 /// \param parent The parent QObject for the stream (optional).
71 ///
72
QXmppIncomingServer(QSslSocket * socket,const QString & domain,QObject * parent)73 QXmppIncomingServer::QXmppIncomingServer(QSslSocket *socket, const QString &domain, QObject *parent)
74 : QXmppStream(parent)
75 {
76
77 d = new QXmppIncomingServerPrivate(this);
78 d->domain = domain;
79
80 if (socket) {
81 connect(socket, &QAbstractSocket::disconnected,
82 this, &QXmppIncomingServer::slotSocketDisconnected);
83
84 setSocket(socket);
85 }
86
87 info(QString("Incoming server connection from %1").arg(d->origin()));
88 }
89
90 /// Destroys the current stream.
91
~QXmppIncomingServer()92 QXmppIncomingServer::~QXmppIncomingServer()
93 {
94 delete d;
95 }
96
97 /// Returns the stream's identifier.
98 ///
99
localStreamId() const100 QString QXmppIncomingServer::localStreamId() const
101 {
102 return d->localStreamId;
103 }
104
105 /// \cond
handleStream(const QDomElement & streamElement)106 void QXmppIncomingServer::handleStream(const QDomElement &streamElement)
107 {
108 const QString from = streamElement.attribute("from");
109 if (!from.isEmpty())
110 info(QString("Incoming server stream from %1 on %2").arg(from, d->origin()));
111
112 // start stream
113 d->localStreamId = QXmppUtils::generateStanzaHash().toLatin1();
114 QString data = QString("<?xml version='1.0'?><stream:stream"
115 " xmlns='%1' xmlns:db='%2' xmlns:stream='%3'"
116 " id='%4' version=\"1.0\">")
117 .arg(
118 ns_server,
119 ns_server_dialback,
120 ns_stream,
121 d->localStreamId);
122 sendData(data.toUtf8());
123
124 // send stream features
125 QXmppStreamFeatures features;
126 if (!socket()->isEncrypted() && !socket()->localCertificate().isNull() && !socket()->privateKey().isNull())
127 features.setTlsMode(QXmppStreamFeatures::Enabled);
128 sendPacket(features);
129 }
130
handleStanza(const QDomElement & stanza)131 void QXmppIncomingServer::handleStanza(const QDomElement &stanza)
132 {
133 const QString ns = stanza.namespaceURI();
134
135 if (QXmppStartTlsPacket::isStartTlsPacket(stanza, QXmppStartTlsPacket::StartTls)) {
136 sendPacket(QXmppStartTlsPacket(QXmppStartTlsPacket::Proceed));
137 socket()->flush();
138 socket()->startServerEncryption();
139 return;
140 } else if (QXmppDialback::isDialback(stanza)) {
141 QXmppDialback request;
142 request.parse(stanza);
143 // check the request is valid
144 if (!request.type().isEmpty() ||
145 request.from().isEmpty() ||
146 request.to() != d->domain ||
147 request.key().isEmpty()) {
148 warning(QString("Invalid dialback received on %1").arg(d->origin()));
149 return;
150 }
151
152 const QString domain = request.from();
153 if (request.command() == QXmppDialback::Result) {
154 debug(QString("Received a dialback result from '%1' on %2").arg(domain, d->origin()));
155
156 // establish dialback connection
157 auto *stream = new QXmppOutgoingServer(d->domain, this);
158 connect(stream, &QXmppOutgoingServer::dialbackResponseReceived,
159 this, &QXmppIncomingServer::slotDialbackResponseReceived);
160 stream->setVerify(d->localStreamId, request.key());
161 stream->connectToHost(domain);
162 } else if (request.command() == QXmppDialback::Verify) {
163 debug(QString("Received a dialback verify from '%1' on %2").arg(domain, d->origin()));
164 emit dialbackRequestReceived(request);
165 }
166
167 } else if (d->authenticated.contains(QXmppUtils::jidToDomain(stanza.attribute("from")))) {
168 // relay stanza if the remote party is authenticated
169 emit elementReceived(stanza);
170 } else {
171 warning(QString("Received an element from unverified domain '%1' on %2").arg(QXmppUtils::jidToDomain(stanza.attribute("from")), d->origin()));
172 disconnectFromHost();
173 }
174 }
175 /// \endcond
176
177 /// Returns true if the socket is connected and the remote server is
178 /// authenticated.
179 ///
180
isConnected() const181 bool QXmppIncomingServer::isConnected() const
182 {
183 return QXmppStream::isConnected() && !d->authenticated.isEmpty();
184 }
185
186 /// Handles a dialback response received from the authority server.
187 ///
188 /// \param response
189 ///
190
slotDialbackResponseReceived(const QXmppDialback & dialback)191 void QXmppIncomingServer::slotDialbackResponseReceived(const QXmppDialback &dialback)
192 {
193 auto *stream = qobject_cast<QXmppOutgoingServer *>(sender());
194 if (!stream ||
195 dialback.command() != QXmppDialback::Verify ||
196 dialback.id() != d->localStreamId ||
197 dialback.from() != stream->remoteDomain())
198 return;
199
200 // relay verify response
201 QXmppDialback response;
202 response.setCommand(QXmppDialback::Result);
203 response.setTo(dialback.from());
204 response.setFrom(d->domain);
205 response.setType(dialback.type());
206 sendPacket(response);
207
208 // check for success
209 if (response.type() == QLatin1String("valid")) {
210 info(QString("Verified incoming domain '%1' on %2").arg(dialback.from(), d->origin()));
211 const bool wasConnected = !d->authenticated.isEmpty();
212 d->authenticated.insert(dialback.from());
213 if (!wasConnected)
214 emit connected();
215 } else {
216 warning(QString("Failed to verify incoming domain '%1' on %2").arg(dialback.from(), d->origin()));
217 disconnectFromHost();
218 }
219
220 // disconnect dialback
221 stream->disconnectFromHost();
222 stream->deleteLater();
223 }
224
slotSocketDisconnected()225 void QXmppIncomingServer::slotSocketDisconnected()
226 {
227 info(QString("Socket disconnected from %1").arg(d->origin()));
228 emit disconnected();
229 }
230