1 /***************************************************************************
2 * Copyright (C) 2005-2020 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 ***************************************************************************/
21 #include "identserver.h"
25 #include "corenetwork.h"
27 IdentServer::IdentServer(QObject* parent)
30 connect(&_server, &QTcpServer::newConnection, this, &IdentServer::incomingConnection);
31 connect(&_v6server, &QTcpServer::newConnection, this, &IdentServer::incomingConnection);
34 bool IdentServer::startListening()
38 uint16_t port = Quassel::optionValue("ident-port").toUShort();
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
44 if (!addr.setAddress(listen_term)) {
45 qCritical() << qPrintable(
46 tr("Invalid listen address %1")
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")
57 .arg(_v6server.serverPort())
62 qWarning() << qPrintable(
63 tr("Could not open IPv6 interface %1:%2: %3")
66 .arg(_v6server.errorString()));
68 case QAbstractSocket::IPv4Protocol:
69 if (_server.listen(addr, port)) {
70 qInfo() << qPrintable(
71 tr("Listening for identd requests on IPv4 %1 port %2")
73 .arg(_server.serverPort())
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")
84 .arg(_server.errorString()));
88 qCritical() << qPrintable(
89 tr("Invalid listen address %1, unknown network protocol")
98 qWarning() << qPrintable(tr("Identd could not open any network interfaces to listen on! No identd functionality will be available"));
104 void IdentServer::stopListening(const QString& msg)
106 bool wasListening = false;
108 if (_server.isListening()) {
112 if (_v6server.isListening()) {
119 qInfo() << "No longer listening for identd clients.";
121 qInfo() << qPrintable(msg);
125 void IdentServer::incomingConnection()
127 auto server = qobject_cast<QTcpServer*>(sender());
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);
136 void IdentServer::respond()
138 auto* socket = qobject_cast<QTcpSocket*>(sender());
141 qint64 transactionId = _socketId;
143 if (!socket->canReadLine()) {
147 QByteArray query = socket->readLine();
148 if (query.endsWith("\r\n"))
150 else if (query.endsWith("\n"))
153 qDebug() << "Received identd query" << query << "from" << socket->peerAddress();
155 QList<QByteArray> split = query.split(',');
157 bool successLocalPort = false;
158 bool successRemotePort = false;
160 quint16 localPort = 0;
161 quint16 remotePort = 0;
162 if (split.length() == 2) {
163 localPort = split[0].trimmed().toUShort(&successLocalPort, 10);
164 remotePort = split[1].trimmed().toUShort(&successRemotePort, 10);
167 Request request{socket, localPort, remotePort, query, transactionId, _requestId++};
168 if (!successLocalPort || !successRemotePort) {
169 request.respondError("INVALID-PORT");
171 else if (responseAvailable(request)) {
174 else if (lowestSocketId() < transactionId) {
175 _requestQueue.emplace_back(request);
178 request.respondError("NO-USER");
182 void Request::respondSuccess(const QString& user)
185 QString data = QString("%1, %2 : USERID : Quassel : %3\r\n")
186 .arg(QString::number(localPort))
187 .arg(QString::number(remotePort))
189 qDebug() << "answering identd request from" << socket->peerAddress() << "with" << data;
190 socket->write(data.toUtf8());
192 QTimer::singleShot(DISCONNECTION_TIMEOUT, socket, &QTcpSocket::close);
196 void Request::respondError(const QString& error)
199 QString data = QString("%1, %2 : ERROR : %3\r\n")
200 .arg(QString::number(localPort))
201 .arg(QString::number(remotePort))
203 qDebug() << "answering identd request from" << socket->peerAddress() << "with" << data;
204 socket->write(data.toUtf8());
206 QTimer::singleShot(DISCONNECTION_TIMEOUT, socket, &QTcpSocket::close);
210 bool IdentServer::responseAvailable(Request request) const
212 if (!_connections.contains(request.localPort)) {
216 request.respondSuccess(_connections[request.localPort]);
220 void IdentServer::addSocket(const CoreIdentity* identity,
221 const QHostAddress& localAddress,
223 const QHostAddress& peerAddress,
227 Q_UNUSED(localAddress)
228 Q_UNUSED(peerAddress)
231 const CoreNetwork* network = qobject_cast<CoreNetwork*>(sender());
232 _connections[localPort] = network->coreSession()->strictCompliantIdent(identity);
234 processWaiting(socketId);
237 void IdentServer::removeSocket(const CoreIdentity* identity,
238 const QHostAddress& localAddress,
240 const QHostAddress& peerAddress,
245 Q_UNUSED(localAddress)
246 Q_UNUSED(peerAddress)
249 _connections.remove(localPort);
250 processWaiting(socketId);
253 qint64 IdentServer::addWaitingSocket()
255 qint64 newSocketId = _socketId++;
256 _waiting.push_back(newSocketId);
260 qint64 IdentServer::lowestSocketId() const
262 if (_waiting.empty()) {
263 return std::numeric_limits<qint64>::max();
266 return _waiting.front();
269 void IdentServer::removeWaitingSocket(qint64 socketId)
271 _waiting.remove(socketId);
274 void IdentServer::processWaiting(qint64 socketId)
276 removeWaitingSocket(socketId);
278 _requestQueue.remove_if([this, socketId](Request request) {
279 if (socketId < request.transactionId && responseAvailable(request)) {
282 else if (lowestSocketId() < request.transactionId) {
286 request.respondError("NO-USER");
292 bool operator==(const Request& a, const Request& b)
294 return a.requestId == b.requestId;