Allow configuring listen address
[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 <limits>
22
23 #include "corenetwork.h"
24 #include "identserver.h"
25 #include "logmessage.h"
26
27 IdentServer::IdentServer(QObject *parent)
28     : QObject(parent)
29 {
30     connect(&_server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
31     connect(&_v6server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
32 }
33
34
35 bool IdentServer::startListening()
36 {
37     bool success = false;
38
39     uint16_t port = Quassel::optionValue("ident-port").toUShort();
40
41     const QString listen = Quassel::optionValue("ident-listen");
42     const QStringList listen_list = listen.split(",", QString::SkipEmptyParts);
43     for (const QString &listen_term : listen_list) { // TODO: handle multiple interfaces for same TCP version gracefully
44         QHostAddress addr;
45         if (!addr.setAddress(listen_term)) {
46             qCritical() << qPrintable(
47                     tr("Invalid listen address %1")
48                             .arg(listen_term)
49             );
50         }
51         else {
52             switch (addr.protocol()) {
53                 case QAbstractSocket::IPv6Protocol:
54                     if (_v6server.listen(addr, port)) {
55                         quInfo() << qPrintable(
56                                 tr("Listening for identd requests on IPv6 %1 port %2")
57                                         .arg(addr.toString())
58                                         .arg(_v6server.serverPort())
59                         );
60                         success = true;
61                     }
62                     else
63                         quWarning() << qPrintable(
64                                 tr("Could not open IPv6 interface %1:%2: %3")
65                                         .arg(addr.toString())
66                                         .arg(port)
67                                         .arg(_v6server.errorString()));
68                     break;
69                 case QAbstractSocket::IPv4Protocol:
70                     if (_server.listen(addr, port)) {
71                         quInfo() << qPrintable(
72                                 tr("Listening for identd requests on IPv4 %1 port %2")
73                                         .arg(addr.toString())
74                                         .arg(_server.serverPort())
75                         );
76                         success = true;
77                     }
78                     else {
79                         // if v6 succeeded on Any, the port will be already in use - don't display the error then
80                         if (!success || _server.serverError() != QAbstractSocket::AddressInUseError)
81                             quWarning() << qPrintable(
82                                     tr("Could not open IPv4 interface %1:%2: %3")
83                                             .arg(addr.toString())
84                                             .arg(port)
85                                             .arg(_server.errorString()));
86                     }
87                     break;
88                 default:
89                     qCritical() << qPrintable(
90                             tr("Invalid listen address %1, unknown network protocol")
91                                     .arg(listen_term)
92                     );
93                     break;
94             }
95         }
96     }
97
98     if (!success) {
99         quError() << qPrintable(tr("Identd could not open any network interfaces to listen on! No identd functionality will be available"));
100     }
101
102     return success;
103 }
104
105
106 void IdentServer::stopListening(const QString &msg)
107 {
108     bool wasListening = false;
109
110     if (_server.isListening()) {
111         wasListening = true;
112         _server.close();
113     }
114     if (_v6server.isListening()) {
115         wasListening = true;
116         _v6server.close();
117     }
118
119     if (wasListening) {
120         if (msg.isEmpty())
121             quInfo() << "No longer listening for identd clients.";
122         else
123             quInfo() << qPrintable(msg);
124     }
125 }
126
127
128 void IdentServer::incomingConnection()
129 {
130     auto server = qobject_cast<QTcpServer *>(sender());
131     Q_ASSERT(server);
132     while (server->hasPendingConnections()) {
133         QTcpSocket *socket = server->nextPendingConnection();
134         connect(socket, SIGNAL(readyRead()), this, SLOT(respond()));
135         connect(socket, SIGNAL(disconnected()), socket, SLOT(deleteLater()));
136     }
137 }
138
139
140 void IdentServer::respond()
141 {
142     QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
143     Q_ASSERT(socket);
144
145     qint64 transactionId = _socketId;
146
147     if (!socket->canReadLine()) {
148         return;
149     }
150
151     QByteArray query = socket->readLine();
152     if (query.endsWith("\r\n"))
153         query.chop(2);
154     else if (query.endsWith("\n"))
155         query.chop(1);
156
157     QList<QByteArray> split = query.split(',');
158
159     bool success = false;
160
161     quint16 localPort = 0;
162     if (!split.empty()) {
163         localPort = split[0].trimmed().toUShort(&success, 10);
164     }
165
166     Request request{socket, localPort, query, transactionId, _requestId++};
167     if (!success) {
168         request.respondError("INVALID-PORT");
169     }
170     else if (responseAvailable(request)) {
171         // success
172     }
173     else if (lowestSocketId() < transactionId) {
174         _requestQueue.emplace_back(request);
175     }
176     else {
177         request.respondError("NO-USER");
178     }
179 }
180
181
182 void Request::respondSuccess(const QString &user)
183 {
184     if (socket) {
185         QString data = query + " : USERID : Quassel : " + user + "\r\n";
186         socket->write(data.toUtf8());
187         socket->flush();
188         socket->close();
189     }
190 }
191
192
193 void Request::respondError(const QString &error)
194 {
195     if (socket) {
196         QString data = query + " : ERROR : " + error + "\r\n";
197         socket->write(data.toUtf8());
198         socket->flush();
199         socket->close();
200     }
201 }
202
203
204 bool IdentServer::responseAvailable(Request request) const
205 {
206     if (!_connections.contains(request.localPort)) {
207         return false;
208     }
209
210     request.respondSuccess(_connections[request.localPort]);
211     return true;
212 }
213
214
215 void IdentServer::addSocket(const CoreIdentity *identity, const QHostAddress &localAddress, quint16 localPort,
216                             const QHostAddress &peerAddress, quint16 peerPort, qint64 socketId)
217 {
218     Q_UNUSED(localAddress)
219     Q_UNUSED(peerAddress)
220     Q_UNUSED(peerPort)
221
222     const CoreNetwork *network = qobject_cast<CoreNetwork *>(sender());
223     _connections[localPort] = network->coreSession()->strictCompliantIdent(identity);;
224     processWaiting(socketId);
225 }
226
227
228 void IdentServer::removeSocket(const CoreIdentity *identity, const QHostAddress &localAddress, quint16 localPort,
229                                const QHostAddress &peerAddress, quint16 peerPort, 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
241 qint64 IdentServer::addWaitingSocket()
242 {
243     qint64 newSocketId = _socketId++;
244     _waiting.push_back(newSocketId);
245     return newSocketId;
246 }
247
248
249 qint64 IdentServer::lowestSocketId() const
250 {
251     if (_waiting.empty()) {
252         return std::numeric_limits<qint64>::max();
253     }
254
255     return _waiting.front();
256 }
257
258
259 void IdentServer::removeWaitingSocket(qint64 socketId)
260 {
261     _waiting.remove(socketId);
262 }
263
264
265 void IdentServer::processWaiting(qint64 socketId)
266 {
267     removeWaitingSocket(socketId);
268
269     _requestQueue.remove_if([this, socketId](Request request) {
270         if (socketId < request.transactionId && responseAvailable(request)) {
271             return true;
272         }
273         else if (lowestSocketId() < request.transactionId) {
274             return false;
275         }
276         else {
277             request.respondError("NO-USER");
278             return true;
279         }
280     });
281 }
282
283
284 bool operator==(const Request &a, const Request &b)
285 {
286     return a.requestId == b.requestId;
287 }