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