Improve identd
[quassel.git] / src / core / identserver.cpp
index cb72a25..1335a6c 100644 (file)
@@ -1,5 +1,5 @@
 /***************************************************************************
- *   Copyright (C) 2005-2018 by the Quassel Project                        *
+ *   Copyright (C) 2005-2019 by the Quassel Project                        *
  *   devel@quassel-irc.org                                                 *
  *                                                                         *
  *   This program is free software; you can redistribute it and/or modify  *
  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
  ***************************************************************************/
 
-#include <logger.h>
-#include <set>
-
-#include "corenetwork.h"
 #include "identserver.h"
 
-IdentServer::IdentServer(bool strict, QObject *parent) : QObject(parent), _strict(strict), _socketId(0), _requestId(0) {
-    connect(&_server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
-    connect(&_v6server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
-}
+#include <limits>
 
-IdentServer::~IdentServer() = default;
+#include "corenetwork.h"
 
-bool IdentServer::startListening() {
-    uint16_t port = Quassel::optionValue("ident-port").toUShort();
+IdentServer::IdentServer(QObject* parent)
+    : QObject(parent)
+{
+    connect(&_server, &QTcpServer::newConnection, this, &IdentServer::incomingConnection);
+    connect(&_v6server, &QTcpServer::newConnection, this, &IdentServer::incomingConnection);
+}
 
+bool IdentServer::startListening()
+{
     bool success = false;
-    if (_v6server.listen(QHostAddress("::1"), port)) {
-        quInfo() << qPrintable(
-                tr("Listening for identd clients on IPv6 %1 port %2")
-                        .arg("::1")
-                        .arg(_v6server.serverPort())
-        );
-
-        success = true;
-    }
 
-    if (_server.listen(QHostAddress("127.0.0.1"), port)) {
-        success = true;
+    uint16_t port = Quassel::optionValue("ident-port").toUShort();
 
-        quInfo() << qPrintable(
-                tr("Listening for identd clients on IPv4 %1 port %2")
-                        .arg("127.0.0.1")
-                        .arg(_server.serverPort())
-        );
+    const QString listen = Quassel::optionValue("ident-listen");
+    const QStringList listen_list = listen.split(",", QString::SkipEmptyParts);
+    for (const QString &listen_term : listen_list) { // TODO: handle multiple interfaces for same TCP version gracefully
+        QHostAddress addr;
+        if (!addr.setAddress(listen_term)) {
+            qCritical() << qPrintable(
+                    tr("Invalid listen address %1")
+                            .arg(listen_term)
+            );
+        }
+        else {
+            switch (addr.protocol()) {
+                case QAbstractSocket::IPv6Protocol:
+                    if (_v6server.listen(addr, port)) {
+                        qInfo() << qPrintable(
+                                tr("Listening for identd requests on IPv6 %1 port %2")
+                                        .arg(addr.toString())
+                                        .arg(_v6server.serverPort())
+                        );
+                        success = true;
+                    }
+                    else
+                        qWarning() << qPrintable(
+                                tr("Could not open IPv6 interface %1:%2: %3")
+                                        .arg(addr.toString())
+                                        .arg(port)
+                                        .arg(_v6server.errorString()));
+                    break;
+                case QAbstractSocket::IPv4Protocol:
+                    if (_server.listen(addr, port)) {
+                        qInfo() << qPrintable(
+                                tr("Listening for identd requests on IPv4 %1 port %2")
+                                        .arg(addr.toString())
+                                        .arg(_server.serverPort())
+                        );
+                        success = true;
+                    }
+                    else {
+                        // if v6 succeeded on Any, the port will be already in use - don't display the error then
+                        if (!success || _server.serverError() != QAbstractSocket::AddressInUseError)
+                            qWarning() << qPrintable(
+                                    tr("Could not open IPv4 interface %1:%2: %3")
+                                            .arg(addr.toString())
+                                            .arg(port)
+                                            .arg(_server.errorString()));
+                    }
+                    break;
+                default:
+                    qCritical() << qPrintable(
+                            tr("Invalid listen address %1, unknown network protocol")
+                                    .arg(listen_term)
+                    );
+                    break;
+            }
+        }
     }
 
     if (!success) {
-        quError() << qPrintable(
-                tr("Identd could not open any network interfaces to listen on! No identd functionality will be available"));
+        qWarning() << qPrintable(tr("Identd could not open any network interfaces to listen on! No identd functionality will be available"));
     }
 
     return success;
 }
 
-void IdentServer::stopListening(const QString &msg) {
+void IdentServer::stopListening(const QString& msg)
+{
     bool wasListening = false;
+
     if (_server.isListening()) {
         wasListening = true;
         _server.close();
@@ -73,25 +113,29 @@ void IdentServer::stopListening(const QString &msg) {
         wasListening = true;
         _v6server.close();
     }
+
     if (wasListening) {
         if (msg.isEmpty())
-            quInfo() << "No longer listening for identd clients.";
+            qInfo() << "No longer listening for identd clients.";
         else
-            quInfo() << qPrintable(msg);
+            qInfo() << qPrintable(msg);
     }
 }
 
-void IdentServer::incomingConnection() {
-    auto *server = qobject_cast<QTcpServer *>(sender());
+void IdentServer::incomingConnection()
+{
+    auto server = qobject_cast<QTcpServer*>(sender());
     Q_ASSERT(server);
     while (server->hasPendingConnections()) {
-        QTcpSocket *socket = server->nextPendingConnection();
-        connect(socket, SIGNAL(readyRead()), this, SLOT(respond()));
+        QTcpSocket* socket = server->nextPendingConnection();
+        connect(socket, &QIODevice::readyRead, this, &IdentServer::respond);
+        connect(socket, &QAbstractSocket::disconnected, socket, &QObject::deleteLater);
     }
 }
 
-void IdentServer::respond() {
-    QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
+void IdentServer::respond()
+{
+    auto* socket = qobject_cast<QTcpSocket*>(sender());
     Q_ASSERT(socket);
 
     qint64 transactionId = _socketId;
@@ -106,71 +150,97 @@ void IdentServer::respond() {
     else if (query.endsWith("\n"))
         query.chop(1);
 
+    qDebug() << "Received identd query" << query << "from" << socket->peerAddress();
+
     QList<QByteArray> split = query.split(',');
 
-    bool success = false;
+    bool successLocalPort = false;
+    bool successRemotePort = false;
 
     quint16 localPort = 0;
-    if (!split.empty()) {
-        localPort = split[0].trimmed().toUShort(&success, 10);
+    quint16 remotePort = 0;
+    if (split.length() == 2) {
+        localPort = split[0].trimmed().toUShort(&successLocalPort, 10);
+        remotePort = split[1].trimmed().toUShort(&successRemotePort, 10);
     }
 
-    Request request{socket, localPort, query, transactionId, _requestId++};
-    if (!success) {
-        responseUnavailable(request);
-    } else if (responseAvailable(request)) {
+    Request request{socket, localPort, remotePort, query, transactionId, _requestId++};
+    if (!successLocalPort || !successRemotePort) {
+        request.respondError("INVALID-PORT");
+    }
+    else if (responseAvailable(request)) {
         // success
-    } else if (hasSocketsBelowId(transactionId)) {
+    }
+    else if (lowestSocketId() < transactionId) {
         _requestQueue.emplace_back(request);
-    } else {
-        responseUnavailable(request);
+    }
+    else {
+        request.respondError("NO-USER");
     }
 }
 
-bool IdentServer::responseAvailable(Request request) {
-    QString user;
-    bool success = true;
-    if (_connections.contains(request.localPort)) {
-        user = _connections[request.localPort];
-    } else {
-        success = false;
+void Request::respondSuccess(const QString& user)
+{
+    if (socket) {
+        QString data = QString("%1, %2 : USERID : Quassel : %3\r\n")
+            .arg(QString::number(localPort))
+            .arg(QString::number(remotePort))
+            .arg(user);
+        qDebug() << "answering identd request from" << socket->peerAddress() << "with" << data;
+        socket->write(data.toUtf8());
+        socket->flush();
+        QTimer::singleShot(DISCONNECTION_TIMEOUT, socket, &QTcpSocket::close);
     }
+}
 
-    QString data;
-    if (success) {
-        data += request.query + " : USERID : Quassel : " + user + "\r\n";
-
-        request.socket->write(data.toUtf8());
-        request.socket->flush();
-        request.socket->close();
+void Request::respondError(const QString& error)
+{
+    if (socket) {
+        QString data = QString("%1, %2 : ERROR : %3\r\n")
+            .arg(QString::number(localPort))
+            .arg(QString::number(remotePort))
+            .arg(error);
+        qDebug() << "answering identd request from" << socket->peerAddress() << "with" << data;
+        socket->write(data.toUtf8());
+        socket->flush();
+        QTimer::singleShot(DISCONNECTION_TIMEOUT, socket, &QTcpSocket::close);
     }
-    return success;
 }
 
-void IdentServer::responseUnavailable(Request request) {
-    QString data = request.query + " : ERROR : NO-USER\r\n";
+bool IdentServer::responseAvailable(Request request) const
+{
+    if (!_connections.contains(request.localPort)) {
+        return false;
+    }
 
-    request.socket->write(data.toUtf8());
-    request.socket->flush();
-    request.socket->close();
+    request.respondSuccess(_connections[request.localPort]);
+    return true;
 }
 
-
-bool IdentServer::addSocket(const CoreIdentity *identity, const QHostAddress &localAddress, quint16 localPort,
-                            const QHostAddress &peerAddress, quint16 peerPort, qint64 socketId) {
+void IdentServer::addSocket(const CoreIdentity* identity,
+                            const QHostAddress& localAddress,
+                            quint16 localPort,
+                            const QHostAddress& peerAddress,
+                            quint16 peerPort,
+                            qint64 socketId)
+{
     Q_UNUSED(localAddress)
     Q_UNUSED(peerAddress)
     Q_UNUSED(peerPort)
 
-    const CoreNetwork *network = qobject_cast<CoreNetwork *>(sender());
-    _connections[localPort] = network->coreSession()->strictCompliantIdent(identity);;
+    const CoreNetwork* network = qobject_cast<CoreNetwork*>(sender());
+    _connections[localPort] = network->coreSession()->strictCompliantIdent(identity);
+
     processWaiting(socketId);
-    return true;
 }
 
-
-bool IdentServer::removeSocket(const CoreIdentity *identity, const QHostAddress &localAddress, quint16 localPort,
-                               const QHostAddress &peerAddress, quint16 peerPort, qint64 socketId) {
+void IdentServer::removeSocket(const CoreIdentity* identity,
+                               const QHostAddress& localAddress,
+                               quint16 localPort,
+                               const QHostAddress& peerAddress,
+                               quint16 peerPort,
+                               qint64 socketId)
+{
     Q_UNUSED(identity)
     Q_UNUSED(localAddress)
     Q_UNUSED(peerAddress)
@@ -178,45 +248,48 @@ bool IdentServer::removeSocket(const CoreIdentity *identity, const QHostAddress
 
     _connections.remove(localPort);
     processWaiting(socketId);
-    return true;
 }
 
-qint64 IdentServer::addWaitingSocket() {
+qint64 IdentServer::addWaitingSocket()
+{
     qint64 newSocketId = _socketId++;
     _waiting.push_back(newSocketId);
     return newSocketId;
 }
 
-bool IdentServer::hasSocketsBelowId(qint64 id) {
-    return std::any_of(_waiting.begin(), _waiting.end(), [=](qint64 socketId) {
-        return socketId <= id;
-    });
+qint64 IdentServer::lowestSocketId() const
+{
+    if (_waiting.empty()) {
+        return std::numeric_limits<qint64>::max();
+    }
+
+    return _waiting.front();
 }
 
-void IdentServer::removeWaitingSocket(qint64 socketId) {
+void IdentServer::removeWaitingSocket(qint64 socketId)
+{
     _waiting.remove(socketId);
 }
 
-void IdentServer::processWaiting(qint64 socketId) {
-    qint64 lowestSocketId = std::numeric_limits<qint64 >::max();
-    for (qint64 id : _waiting) {
-        if (id < lowestSocketId) {
-            lowestSocketId = id;
-        }
-    }
+void IdentServer::processWaiting(qint64 socketId)
+{
     removeWaitingSocket(socketId);
-    _requestQueue.remove_if([=](Request request) {
-        if (request.transactionId < lowestSocketId) {
-            responseUnavailable(request);
+
+    _requestQueue.remove_if([this, socketId](Request request) {
+        if (socketId < request.transactionId && responseAvailable(request)) {
             return true;
-        } else if (request.transactionId > socketId) {
-            return responseAvailable(request);
-        } else {
+        }
+        else if (lowestSocketId() < request.transactionId) {
             return false;
         }
+        else {
+            request.respondError("NO-USER");
+            return true;
+        }
     });
 }
 
-bool operator==(const Request &a, const Request &b) {
+bool operator==(const Request& a, const Request& b)
+{
     return a.requestId == b.requestId;
 }