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 ***************************************************************************/
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 QList<QByteArray> split = query.split(',');
155 bool success = false;
157 quint16 localPort = 0;
158 if (!split.empty()) {
159 localPort = split[0].trimmed().toUShort(&success, 10);
162 Request request{socket, localPort, query, transactionId, _requestId++};
164 request.respondError("INVALID-PORT");
166 else if (responseAvailable(request)) {
169 else if (lowestSocketId() < transactionId) {
170 _requestQueue.emplace_back(request);
173 request.respondError("NO-USER");
177 void Request::respondSuccess(const QString& user)
180 QString data = query + " : USERID : Quassel : " + user + "\r\n";
181 socket->write(data.toUtf8());
187 void Request::respondError(const QString& error)
190 QString data = query + " : ERROR : " + error + "\r\n";
191 socket->write(data.toUtf8());
197 bool IdentServer::responseAvailable(Request request) const
199 if (!_connections.contains(request.localPort)) {
203 request.respondSuccess(_connections[request.localPort]);
207 void IdentServer::addSocket(const CoreIdentity* identity,
208 const QHostAddress& localAddress,
210 const QHostAddress& peerAddress,
214 Q_UNUSED(localAddress)
215 Q_UNUSED(peerAddress)
218 const CoreNetwork* network = qobject_cast<CoreNetwork*>(sender());
219 _connections[localPort] = network->coreSession()->strictCompliantIdent(identity);
221 processWaiting(socketId);
224 void IdentServer::removeSocket(const CoreIdentity* identity,
225 const QHostAddress& localAddress,
227 const QHostAddress& peerAddress,
232 Q_UNUSED(localAddress)
233 Q_UNUSED(peerAddress)
236 _connections.remove(localPort);
237 processWaiting(socketId);
240 qint64 IdentServer::addWaitingSocket()
242 qint64 newSocketId = _socketId++;
243 _waiting.push_back(newSocketId);
247 qint64 IdentServer::lowestSocketId() const
249 if (_waiting.empty()) {
250 return std::numeric_limits<qint64>::max();
253 return _waiting.front();
256 void IdentServer::removeWaitingSocket(qint64 socketId)
258 _waiting.remove(socketId);
261 void IdentServer::processWaiting(qint64 socketId)
263 removeWaitingSocket(socketId);
265 _requestQueue.remove_if([this, socketId](Request request) {
266 if (socketId < request.transactionId && responseAvailable(request)) {
269 else if (lowestSocketId() < request.transactionId) {
273 request.respondError("NO-USER");
279 bool operator==(const Request& a, const Request& b)
281 return a.requestId == b.requestId;