62d68a9c14ba199d24bc840de64ce6aabe45df0a
[quassel.git] / src / core / identserver.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2019 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     QList<QByteArray> split = query.split(',');
154
155     bool success = false;
156
157     quint16 localPort = 0;
158     if (!split.empty()) {
159         localPort = split[0].trimmed().toUShort(&success, 10);
160     }
161
162     Request request{socket, localPort, query, transactionId, _requestId++};
163     if (!success) {
164         request.respondError("INVALID-PORT");
165     }
166     else if (responseAvailable(request)) {
167         // success
168     }
169     else if (lowestSocketId() < transactionId) {
170         _requestQueue.emplace_back(request);
171     }
172     else {
173         request.respondError("NO-USER");
174     }
175 }
176
177 void Request::respondSuccess(const QString& user)
178 {
179     if (socket) {
180         QString data = query + " : USERID : Quassel : " + user + "\r\n";
181         socket->write(data.toUtf8());
182         socket->flush();
183         socket->close();
184     }
185 }
186
187 void Request::respondError(const QString& error)
188 {
189     if (socket) {
190         QString data = query + " : ERROR : " + error + "\r\n";
191         socket->write(data.toUtf8());
192         socket->flush();
193         socket->close();
194     }
195 }
196
197 bool IdentServer::responseAvailable(Request request) const
198 {
199     if (!_connections.contains(request.localPort)) {
200         return false;
201     }
202
203     request.respondSuccess(_connections[request.localPort]);
204     return true;
205 }
206
207 void IdentServer::addSocket(const CoreIdentity* identity,
208                             const QHostAddress& localAddress,
209                             quint16 localPort,
210                             const QHostAddress& peerAddress,
211                             quint16 peerPort,
212                             qint64 socketId)
213 {
214     Q_UNUSED(localAddress)
215     Q_UNUSED(peerAddress)
216     Q_UNUSED(peerPort)
217
218     const CoreNetwork* network = qobject_cast<CoreNetwork*>(sender());
219     _connections[localPort] = network->coreSession()->strictCompliantIdent(identity);
220     ;
221     processWaiting(socketId);
222 }
223
224 void IdentServer::removeSocket(const CoreIdentity* identity,
225                                const QHostAddress& localAddress,
226                                quint16 localPort,
227                                const QHostAddress& peerAddress,
228                                quint16 peerPort,
229                                qint64 socketId)
230 {
231     Q_UNUSED(identity)
232     Q_UNUSED(localAddress)
233     Q_UNUSED(peerAddress)
234     Q_UNUSED(peerPort)
235
236     _connections.remove(localPort);
237     processWaiting(socketId);
238 }
239
240 qint64 IdentServer::addWaitingSocket()
241 {
242     qint64 newSocketId = _socketId++;
243     _waiting.push_back(newSocketId);
244     return newSocketId;
245 }
246
247 qint64 IdentServer::lowestSocketId() const
248 {
249     if (_waiting.empty()) {
250         return std::numeric_limits<qint64>::max();
251     }
252
253     return _waiting.front();
254 }
255
256 void IdentServer::removeWaitingSocket(qint64 socketId)
257 {
258     _waiting.remove(socketId);
259 }
260
261 void IdentServer::processWaiting(qint64 socketId)
262 {
263     removeWaitingSocket(socketId);
264
265     _requestQueue.remove_if([this, socketId](Request request) {
266         if (socketId < request.transactionId && responseAvailable(request)) {
267             return true;
268         }
269         else if (lowestSocketId() < request.transactionId) {
270             return false;
271         }
272         else {
273             request.respondError("NO-USER");
274             return true;
275         }
276     });
277 }
278
279 bool operator==(const Request& a, const Request& b)
280 {
281     return a.requestId == b.requestId;
282 }