core: Remove slots from storage APIs
[quassel.git] / src / core / identserver.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2020 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) version 3.                                           *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
19  ***************************************************************************/
20
21 #include "identserver.h"
22
23 #include <limits>
24
25 #include "corenetwork.h"
26
27 IdentServer::IdentServer(QObject* parent)
28     : QObject(parent)
29 {
30     connect(&_server, &QTcpServer::newConnection, this, &IdentServer::incomingConnection);
31     connect(&_v6server, &QTcpServer::newConnection, this, &IdentServer::incomingConnection);
32 }
33
34 bool IdentServer::startListening()
35 {
36     bool success = false;
37
38     uint16_t port = Quassel::optionValue("ident-port").toUShort();
39
40     const QString listen = Quassel::optionValue("ident-listen");
41     const QStringList listen_list = listen.split(",", QString::SkipEmptyParts);
42     for (const QString &listen_term : listen_list) { // TODO: handle multiple interfaces for same TCP version gracefully
43         QHostAddress addr;
44         if (!addr.setAddress(listen_term)) {
45             qCritical() << qPrintable(
46                     tr("Invalid listen address %1")
47                             .arg(listen_term)
48             );
49         }
50         else {
51             switch (addr.protocol()) {
52                 case QAbstractSocket::IPv6Protocol:
53                     if (_v6server.listen(addr, port)) {
54                         qInfo() << qPrintable(
55                                 tr("Listening for identd requests on IPv6 %1 port %2")
56                                         .arg(addr.toString())
57                                         .arg(_v6server.serverPort())
58                         );
59                         success = true;
60                     }
61                     else
62                         qWarning() << qPrintable(
63                                 tr("Could not open IPv6 interface %1:%2: %3")
64                                         .arg(addr.toString())
65                                         .arg(port)
66                                         .arg(_v6server.errorString()));
67                     break;
68                 case QAbstractSocket::IPv4Protocol:
69                     if (_server.listen(addr, port)) {
70                         qInfo() << qPrintable(
71                                 tr("Listening for identd requests on IPv4 %1 port %2")
72                                         .arg(addr.toString())
73                                         .arg(_server.serverPort())
74                         );
75                         success = true;
76                     }
77                     else {
78                         // if v6 succeeded on Any, the port will be already in use - don't display the error then
79                         if (!success || _server.serverError() != QAbstractSocket::AddressInUseError)
80                             qWarning() << qPrintable(
81                                     tr("Could not open IPv4 interface %1:%2: %3")
82                                             .arg(addr.toString())
83                                             .arg(port)
84                                             .arg(_server.errorString()));
85                     }
86                     break;
87                 default:
88                     qCritical() << qPrintable(
89                             tr("Invalid listen address %1, unknown network protocol")
90                                     .arg(listen_term)
91                     );
92                     break;
93             }
94         }
95     }
96
97     if (!success) {
98         qWarning() << qPrintable(tr("Identd could not open any network interfaces to listen on! No identd functionality will be available"));
99     }
100
101     return success;
102 }
103
104 void IdentServer::stopListening(const QString& msg)
105 {
106     bool wasListening = false;
107
108     if (_server.isListening()) {
109         wasListening = true;
110         _server.close();
111     }
112     if (_v6server.isListening()) {
113         wasListening = true;
114         _v6server.close();
115     }
116
117     if (wasListening) {
118         if (msg.isEmpty())
119             qInfo() << "No longer listening for identd clients.";
120         else
121             qInfo() << qPrintable(msg);
122     }
123 }
124
125 void IdentServer::incomingConnection()
126 {
127     auto server = qobject_cast<QTcpServer*>(sender());
128     Q_ASSERT(server);
129     while (server->hasPendingConnections()) {
130         QTcpSocket* socket = server->nextPendingConnection();
131         connect(socket, &QIODevice::readyRead, this, &IdentServer::respond);
132         connect(socket, &QAbstractSocket::disconnected, socket, &QObject::deleteLater);
133     }
134 }
135
136 void IdentServer::respond()
137 {
138     auto* socket = qobject_cast<QTcpSocket*>(sender());
139     Q_ASSERT(socket);
140
141     qint64 transactionId = _socketId;
142
143     if (!socket->canReadLine()) {
144         return;
145     }
146
147     QByteArray query = socket->readLine();
148     if (query.endsWith("\r\n"))
149         query.chop(2);
150     else if (query.endsWith("\n"))
151         query.chop(1);
152
153     qDebug() << "Received identd query" << query << "from" << socket->peerAddress();
154
155     QList<QByteArray> split = query.split(',');
156
157     bool successLocalPort = false;
158     bool successRemotePort = false;
159
160     quint16 localPort = 0;
161     quint16 remotePort = 0;
162     if (split.length() == 2) {
163         localPort = split[0].trimmed().toUShort(&successLocalPort, 10);
164         remotePort = split[1].trimmed().toUShort(&successRemotePort, 10);
165     }
166
167     Request request{socket, localPort, remotePort, query, transactionId, _requestId++};
168     if (!successLocalPort || !successRemotePort) {
169         request.respondError("INVALID-PORT");
170     }
171     else if (responseAvailable(request)) {
172         // success
173     }
174     else if (lowestSocketId() < transactionId) {
175         _requestQueue.emplace_back(request);
176     }
177     else {
178         request.respondError("NO-USER");
179     }
180 }
181
182 void Request::respondSuccess(const QString& user)
183 {
184     if (socket) {
185         QString data = QString("%1, %2 : USERID : Quassel : %3\r\n")
186             .arg(QString::number(localPort))
187             .arg(QString::number(remotePort))
188             .arg(user);
189         qDebug() << "answering identd request from" << socket->peerAddress() << "with" << data;
190         socket->write(data.toUtf8());
191         socket->flush();
192         QTimer::singleShot(DISCONNECTION_TIMEOUT, socket, &QTcpSocket::close);
193     }
194 }
195
196 void Request::respondError(const QString& error)
197 {
198     if (socket) {
199         QString data = QString("%1, %2 : ERROR : %3\r\n")
200             .arg(QString::number(localPort))
201             .arg(QString::number(remotePort))
202             .arg(error);
203         qDebug() << "answering identd request from" << socket->peerAddress() << "with" << data;
204         socket->write(data.toUtf8());
205         socket->flush();
206         QTimer::singleShot(DISCONNECTION_TIMEOUT, socket, &QTcpSocket::close);
207     }
208 }
209
210 bool IdentServer::responseAvailable(Request request) const
211 {
212     if (!_connections.contains(request.localPort)) {
213         return false;
214     }
215
216     request.respondSuccess(_connections[request.localPort]);
217     return true;
218 }
219
220 void IdentServer::addSocket(const CoreIdentity* identity,
221                             const QHostAddress& localAddress,
222                             quint16 localPort,
223                             const QHostAddress& peerAddress,
224                             quint16 peerPort,
225                             qint64 socketId)
226 {
227     Q_UNUSED(localAddress)
228     Q_UNUSED(peerAddress)
229     Q_UNUSED(peerPort)
230
231     const CoreNetwork* network = qobject_cast<CoreNetwork*>(sender());
232     _connections[localPort] = network->coreSession()->strictCompliantIdent(identity);
233
234     processWaiting(socketId);
235 }
236
237 void IdentServer::removeSocket(const CoreIdentity* identity,
238                                const QHostAddress& localAddress,
239                                quint16 localPort,
240                                const QHostAddress& peerAddress,
241                                quint16 peerPort,
242                                qint64 socketId)
243 {
244     Q_UNUSED(identity)
245     Q_UNUSED(localAddress)
246     Q_UNUSED(peerAddress)
247     Q_UNUSED(peerPort)
248
249     _connections.remove(localPort);
250     processWaiting(socketId);
251 }
252
253 qint64 IdentServer::addWaitingSocket()
254 {
255     qint64 newSocketId = _socketId++;
256     _waiting.push_back(newSocketId);
257     return newSocketId;
258 }
259
260 qint64 IdentServer::lowestSocketId() const
261 {
262     if (_waiting.empty()) {
263         return std::numeric_limits<qint64>::max();
264     }
265
266     return _waiting.front();
267 }
268
269 void IdentServer::removeWaitingSocket(qint64 socketId)
270 {
271     _waiting.remove(socketId);
272 }
273
274 void IdentServer::processWaiting(qint64 socketId)
275 {
276     removeWaitingSocket(socketId);
277
278     _requestQueue.remove_if([this, socketId](Request request) {
279         if (socketId < request.transactionId && responseAvailable(request)) {
280             return true;
281         }
282         else if (lowestSocketId() < request.transactionId) {
283             return false;
284         }
285         else {
286             request.respondError("NO-USER");
287             return true;
288         }
289     });
290 }
291
292 bool operator==(const Request& a, const Request& b)
293 {
294     return a.requestId == b.requestId;
295 }