c68d119e2a43e33a6979df32e280d2be8c714187
[quassel.git] / src / core / identserver.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2018 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 "logger.h"
26
27 IdentServer::IdentServer(bool strict, QObject *parent)
28     : QObject(parent)
29     , _strict(strict)
30 {
31     connect(&_server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
32     connect(&_v6server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
33 }
34
35
36 bool IdentServer::startListening()
37 {
38     uint16_t port = Quassel::optionValue("ident-port").toUShort();
39
40     bool success = false;
41     if (_v6server.listen(QHostAddress("::1"), port)) {
42         quInfo() << qPrintable(
43                 tr("Listening for identd clients on IPv6 %1 port %2")
44                         .arg("::1")
45                         .arg(_v6server.serverPort())
46         );
47
48         success = true;
49     }
50
51     if (_server.listen(QHostAddress("127.0.0.1"), port)) {
52         quInfo() << qPrintable(
53                 tr("Listening for identd clients on IPv4 %1 port %2")
54                         .arg("127.0.0.1")
55                         .arg(_server.serverPort())
56         );
57
58         success = true;
59     }
60
61     if (!success) {
62         quError() << qPrintable(tr("Identd could not open any network interfaces to listen on! No identd functionality will be available"));
63     }
64
65     return success;
66 }
67
68
69 void IdentServer::stopListening(const QString &msg)
70 {
71     bool wasListening = false;
72
73     if (_server.isListening()) {
74         wasListening = true;
75         _server.close();
76     }
77     if (_v6server.isListening()) {
78         wasListening = true;
79         _v6server.close();
80     }
81
82     if (wasListening) {
83         if (msg.isEmpty())
84             quInfo() << "No longer listening for identd clients.";
85         else
86             quInfo() << qPrintable(msg);
87     }
88 }
89
90
91 void IdentServer::incomingConnection()
92 {
93     auto server = qobject_cast<QTcpServer *>(sender());
94     Q_ASSERT(server);
95     while (server->hasPendingConnections()) {
96         QTcpSocket *socket = server->nextPendingConnection();
97         connect(socket, SIGNAL(readyRead()), this, SLOT(respond()));
98         connect(socket, SIGNAL(disconnected()), socket, SLOT(deleteLater()));
99     }
100 }
101
102
103 void IdentServer::respond()
104 {
105     QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
106     Q_ASSERT(socket);
107
108     qint64 transactionId = _socketId;
109
110     if (!socket->canReadLine()) {
111         return;
112     }
113
114     QByteArray query = socket->readLine();
115     if (query.endsWith("\r\n"))
116         query.chop(2);
117     else if (query.endsWith("\n"))
118         query.chop(1);
119
120     QList<QByteArray> split = query.split(',');
121
122     bool success = false;
123
124     quint16 localPort = 0;
125     if (!split.empty()) {
126         localPort = split[0].trimmed().toUShort(&success, 10);
127     }
128
129     Request request{socket, localPort, query, transactionId, _requestId++};
130     if (!success) {
131         request.respondError("INVALID-PORT");
132     }
133     else if (responseAvailable(request)) {
134         // success
135     }
136     else if (lowestSocketId() < transactionId) {
137         _requestQueue.emplace_back(request);
138     }
139     else {
140         request.respondError("NO-USER");
141     }
142 }
143
144
145 void Request::respondSuccess(const QString &user)
146 {
147     if (socket) {
148         QString data = query + " : USERID : Quassel : " + user + "\r\n";
149         socket->write(data.toUtf8());
150         socket->flush();
151         socket->close();
152     }
153 }
154
155
156 void Request::respondError(const QString &error)
157 {
158     if (socket) {
159         QString data = query + " : ERROR : " + error + "\r\n";
160         socket->write(data.toUtf8());
161         socket->flush();
162         socket->close();
163     }
164 }
165
166
167 bool IdentServer::responseAvailable(Request request) const
168 {
169     if (!_connections.contains(request.localPort)) {
170         return false;
171     }
172
173     request.respondSuccess(_connections[request.localPort]);
174     return true;
175 }
176
177
178 void IdentServer::addSocket(const CoreIdentity *identity, const QHostAddress &localAddress, quint16 localPort,
179                             const QHostAddress &peerAddress, quint16 peerPort, qint64 socketId)
180 {
181     Q_UNUSED(localAddress)
182     Q_UNUSED(peerAddress)
183     Q_UNUSED(peerPort)
184
185     const CoreNetwork *network = qobject_cast<CoreNetwork *>(sender());
186     _connections[localPort] = network->coreSession()->strictCompliantIdent(identity);;
187     processWaiting(socketId);
188 }
189
190
191 void IdentServer::removeSocket(const CoreIdentity *identity, const QHostAddress &localAddress, quint16 localPort,
192                                const QHostAddress &peerAddress, quint16 peerPort, qint64 socketId)
193 {
194     Q_UNUSED(identity)
195     Q_UNUSED(localAddress)
196     Q_UNUSED(peerAddress)
197     Q_UNUSED(peerPort)
198
199     _connections.remove(localPort);
200     processWaiting(socketId);
201 }
202
203
204 qint64 IdentServer::addWaitingSocket()
205 {
206     qint64 newSocketId = _socketId++;
207     _waiting.push_back(newSocketId);
208     return newSocketId;
209 }
210
211
212 qint64 IdentServer::lowestSocketId() const
213 {
214     if (_waiting.empty()) {
215         return std::numeric_limits<qint64>::max();
216     }
217
218     return _waiting.front();
219 }
220
221
222 void IdentServer::removeWaitingSocket(qint64 socketId)
223 {
224     _waiting.remove(socketId);
225 }
226
227
228 void IdentServer::processWaiting(qint64 socketId)
229 {
230     removeWaitingSocket(socketId);
231
232     _requestQueue.remove_if([this, socketId](Request request) {
233         if (socketId < request.transactionId && responseAvailable(request)) {
234             return true;
235         }
236         else if (lowestSocketId() < request.transactionId) {
237             return false;
238         }
239         else {
240             request.respondError("NO-USER");
241             return true;
242         }
243     });
244 }
245
246
247 bool operator==(const Request &a, const Request &b)
248 {
249     return a.requestId == b.requestId;
250 }