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