Make the identd listen on all adresses
[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     uint16_t port = Quassel::optionValue("ident-port").toUShort();
37
38     bool success = false;
39     if (_v6server.listen(QHostAddress("::"), port)) {
40         qInfo() << qPrintable(tr("Listening for identd clients on IPv6 %1 port %2").arg("::").arg(_v6server.serverPort()));
41
42         success = true;
43     }
44
45     if (_server.listen(QHostAddress("0.0.0.1"), port)) {
46         qInfo() << qPrintable(tr("Listening for identd clients on IPv4 %1 port %2").arg("0.0.0.1").arg(_server.serverPort()));
47
48         success = true;
49     }
50
51     if (!success) {
52         qWarning() << qPrintable(tr("Identd could not open any network interfaces to listen on! No identd functionality will be available"));
53     }
54
55     return success;
56 }
57
58 void IdentServer::stopListening(const QString& msg)
59 {
60     bool wasListening = false;
61
62     if (_server.isListening()) {
63         wasListening = true;
64         _server.close();
65     }
66     if (_v6server.isListening()) {
67         wasListening = true;
68         _v6server.close();
69     }
70
71     if (wasListening) {
72         if (msg.isEmpty())
73             qInfo() << "No longer listening for identd clients.";
74         else
75             qInfo() << qPrintable(msg);
76     }
77 }
78
79 void IdentServer::incomingConnection()
80 {
81     auto server = qobject_cast<QTcpServer*>(sender());
82     Q_ASSERT(server);
83     while (server->hasPendingConnections()) {
84         QTcpSocket* socket = server->nextPendingConnection();
85         connect(socket, &QIODevice::readyRead, this, &IdentServer::respond);
86         connect(socket, &QAbstractSocket::disconnected, socket, &QObject::deleteLater);
87     }
88 }
89
90 void IdentServer::respond()
91 {
92     auto* socket = qobject_cast<QTcpSocket*>(sender());
93     Q_ASSERT(socket);
94
95     qint64 transactionId = _socketId;
96
97     if (!socket->canReadLine()) {
98         return;
99     }
100
101     QByteArray query = socket->readLine();
102     if (query.endsWith("\r\n"))
103         query.chop(2);
104     else if (query.endsWith("\n"))
105         query.chop(1);
106
107     QList<QByteArray> split = query.split(',');
108
109     bool success = false;
110
111     quint16 localPort = 0;
112     if (!split.empty()) {
113         localPort = split[0].trimmed().toUShort(&success, 10);
114     }
115
116     Request request{socket, localPort, query, transactionId, _requestId++};
117     if (!success) {
118         request.respondError("INVALID-PORT");
119     }
120     else if (responseAvailable(request)) {
121         // success
122     }
123     else if (lowestSocketId() < transactionId) {
124         _requestQueue.emplace_back(request);
125     }
126     else {
127         request.respondError("NO-USER");
128     }
129 }
130
131 void Request::respondSuccess(const QString& user)
132 {
133     if (socket) {
134         QString data = query + " : USERID : Quassel : " + user + "\r\n";
135         socket->write(data.toUtf8());
136         socket->flush();
137         socket->close();
138     }
139 }
140
141 void Request::respondError(const QString& error)
142 {
143     if (socket) {
144         QString data = query + " : ERROR : " + error + "\r\n";
145         socket->write(data.toUtf8());
146         socket->flush();
147         socket->close();
148     }
149 }
150
151 bool IdentServer::responseAvailable(Request request) const
152 {
153     if (!_connections.contains(request.localPort)) {
154         return false;
155     }
156
157     request.respondSuccess(_connections[request.localPort]);
158     return true;
159 }
160
161 void IdentServer::addSocket(const CoreIdentity* identity,
162                             const QHostAddress& localAddress,
163                             quint16 localPort,
164                             const QHostAddress& peerAddress,
165                             quint16 peerPort,
166                             qint64 socketId)
167 {
168     Q_UNUSED(localAddress)
169     Q_UNUSED(peerAddress)
170     Q_UNUSED(peerPort)
171
172     const CoreNetwork* network = qobject_cast<CoreNetwork*>(sender());
173     _connections[localPort] = network->coreSession()->strictCompliantIdent(identity);
174     ;
175     processWaiting(socketId);
176 }
177
178 void IdentServer::removeSocket(const CoreIdentity* identity,
179                                const QHostAddress& localAddress,
180                                quint16 localPort,
181                                const QHostAddress& peerAddress,
182                                quint16 peerPort,
183                                qint64 socketId)
184 {
185     Q_UNUSED(identity)
186     Q_UNUSED(localAddress)
187     Q_UNUSED(peerAddress)
188     Q_UNUSED(peerPort)
189
190     _connections.remove(localPort);
191     processWaiting(socketId);
192 }
193
194 qint64 IdentServer::addWaitingSocket()
195 {
196     qint64 newSocketId = _socketId++;
197     _waiting.push_back(newSocketId);
198     return newSocketId;
199 }
200
201 qint64 IdentServer::lowestSocketId() const
202 {
203     if (_waiting.empty()) {
204         return std::numeric_limits<qint64>::max();
205     }
206
207     return _waiting.front();
208 }
209
210 void IdentServer::removeWaitingSocket(qint64 socketId)
211 {
212     _waiting.remove(socketId);
213 }
214
215 void IdentServer::processWaiting(qint64 socketId)
216 {
217     removeWaitingSocket(socketId);
218
219     _requestQueue.remove_if([this, socketId](Request request) {
220         if (socketId < request.transactionId && responseAvailable(request)) {
221             return true;
222         }
223         else if (lowestSocketId() < request.transactionId) {
224             return false;
225         }
226         else {
227             request.respondError("NO-USER");
228             return true;
229         }
230     });
231 }
232
233 bool operator==(const Request& a, const Request& b)
234 {
235     return a.requestId == b.requestId;
236 }