1 /***************************************************************************
2 * Copyright (C) 2005-2019 by the Quassel Project *
3 * devel@quassel-irc.org *
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. *
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. *
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 ***************************************************************************/
23 #include "corenetwork.h"
24 #include "identserver.h"
25 #include "logmessage.h"
27 IdentServer::IdentServer(QObject *parent)
30 connect(&_server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
31 connect(&_v6server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
35 bool IdentServer::startListening()
39 uint16_t port = Quassel::optionValue("ident-port").toUShort();
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
45 if (!addr.setAddress(listen_term)) {
46 qCritical() << qPrintable(
47 tr("Invalid listen address %1")
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")
58 .arg(_v6server.serverPort())
63 quWarning() << qPrintable(
64 tr("Could not open IPv6 interface %1:%2: %3")
67 .arg(_v6server.errorString()));
69 case QAbstractSocket::IPv4Protocol:
70 if (_server.listen(addr, port)) {
71 quInfo() << qPrintable(
72 tr("Listening for identd requests on IPv4 %1 port %2")
74 .arg(_server.serverPort())
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")
85 .arg(_server.errorString()));
89 qCritical() << qPrintable(
90 tr("Invalid listen address %1, unknown network protocol")
99 quError() << qPrintable(tr("Identd could not open any network interfaces to listen on! No identd functionality will be available"));
106 void IdentServer::stopListening(const QString &msg)
108 bool wasListening = false;
110 if (_server.isListening()) {
114 if (_v6server.isListening()) {
121 quInfo() << "No longer listening for identd clients.";
123 quInfo() << qPrintable(msg);
128 void IdentServer::incomingConnection()
130 auto server = qobject_cast<QTcpServer *>(sender());
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()));
140 void IdentServer::respond()
142 QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
145 qint64 transactionId = _socketId;
147 if (!socket->canReadLine()) {
151 QByteArray query = socket->readLine();
152 if (query.endsWith("\r\n"))
154 else if (query.endsWith("\n"))
157 QList<QByteArray> split = query.split(',');
159 bool success = false;
161 quint16 localPort = 0;
162 if (!split.empty()) {
163 localPort = split[0].trimmed().toUShort(&success, 10);
166 Request request{socket, localPort, query, transactionId, _requestId++};
168 request.respondError("INVALID-PORT");
170 else if (responseAvailable(request)) {
173 else if (lowestSocketId() < transactionId) {
174 _requestQueue.emplace_back(request);
177 request.respondError("NO-USER");
182 void Request::respondSuccess(const QString &user)
185 QString data = query + " : USERID : Quassel : " + user + "\r\n";
186 socket->write(data.toUtf8());
193 void Request::respondError(const QString &error)
196 QString data = query + " : ERROR : " + error + "\r\n";
197 socket->write(data.toUtf8());
204 bool IdentServer::responseAvailable(Request request) const
206 if (!_connections.contains(request.localPort)) {
210 request.respondSuccess(_connections[request.localPort]);
215 void IdentServer::addSocket(const CoreIdentity *identity, const QHostAddress &localAddress, quint16 localPort,
216 const QHostAddress &peerAddress, quint16 peerPort, qint64 socketId)
218 Q_UNUSED(localAddress)
219 Q_UNUSED(peerAddress)
222 const CoreNetwork *network = qobject_cast<CoreNetwork *>(sender());
223 _connections[localPort] = network->coreSession()->strictCompliantIdent(identity);;
224 processWaiting(socketId);
228 void IdentServer::removeSocket(const CoreIdentity *identity, const QHostAddress &localAddress, quint16 localPort,
229 const QHostAddress &peerAddress, quint16 peerPort, qint64 socketId)
232 Q_UNUSED(localAddress)
233 Q_UNUSED(peerAddress)
236 _connections.remove(localPort);
237 processWaiting(socketId);
241 qint64 IdentServer::addWaitingSocket()
243 qint64 newSocketId = _socketId++;
244 _waiting.push_back(newSocketId);
249 qint64 IdentServer::lowestSocketId() const
251 if (_waiting.empty()) {
252 return std::numeric_limits<qint64>::max();
255 return _waiting.front();
259 void IdentServer::removeWaitingSocket(qint64 socketId)
261 _waiting.remove(socketId);
265 void IdentServer::processWaiting(qint64 socketId)
267 removeWaitingSocket(socketId);
269 _requestQueue.remove_if([this, socketId](Request request) {
270 if (socketId < request.transactionId && responseAvailable(request)) {
273 else if (lowestSocketId() < request.transactionId) {
277 request.respondError("NO-USER");
284 bool operator==(const Request &a, const Request &b)
286 return a.requestId == b.requestId;