1 | /*************************************************************************** |
2 | * Copyright (C) 2005-2014 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 "coreauthhandler.h" |
22 | |
23 | #ifdef HAVE_SSL |
24 | # include <QSslSocket> |
25 | #endif |
26 | |
27 | #include "core.h" |
28 | #include "logger.h" |
29 | |
30 | using namespace Protocol; |
31 | |
32 | CoreAuthHandler::CoreAuthHandler(QTcpSocket *socket, QObject *parent) |
33 | : AuthHandler(parent), |
34 | _peer(0), |
35 | _magicReceived(false), |
36 | _legacy(false), |
37 | _clientRegistered(false), |
38 | _connectionFeatures(0) |
39 | { |
40 | setSocket(socket); |
41 | connect(socket, SIGNAL(readyRead()), SLOT(onReadyRead())); |
42 | |
43 | // TODO: Timeout for the handshake phase |
44 | |
45 | } |
46 | |
47 | |
48 | void CoreAuthHandler::onReadyRead() |
49 | { |
50 | if (socket()->bytesAvailable() < 4) |
51 | return; |
52 | |
53 | // once we have selected a peer, we certainly don't want to read more data! |
54 | if (_peer) |
55 | return; |
56 | |
57 | if (!_magicReceived) { |
58 | quint32 magic; |
59 | socket()->peek((char*)&magic, 4); |
60 | magic = qFromBigEndian<quint32>(magic); |
61 | |
62 | if ((magic & 0xffffff00) != Protocol::magic) { |
63 | // no magic, assume legacy protocol |
64 | qDebug() << "Legacy client detected, switching to compatibility mode" ; |
65 | _legacy = true; |
66 | RemotePeer *peer = PeerFactory::createPeer(PeerFactory::ProtoDescriptor(Protocol::LegacyProtocol, 0), this, socket(), Compressor::NoCompression, this); |
67 | connect(peer, SIGNAL(protocolVersionMismatch(int,int)), SLOT(onProtocolVersionMismatch(int,int))); |
68 | setPeer(peer); |
69 | return; |
70 | } |
71 | |
72 | _magicReceived = true; |
73 | quint8 features = magic & 0xff; |
74 | // figure out which connection features we'll use based on the client's support |
75 | if (Core::sslSupported() && (features & Protocol::Encryption)) |
76 | _connectionFeatures |= Protocol::Encryption; |
77 | if (features & Protocol::Compression) |
78 | _connectionFeatures |= Protocol::Compression; |
79 | |
80 | socket()->read((char*)&magic, 4); // read the 4 bytes we've just peeked at |
81 | } |
82 | |
83 | // read the list of protocols supported by the client |
84 | while (socket()->bytesAvailable() >= 4) { |
85 | quint32 data; |
86 | socket()->read((char*)&data, 4); |
87 | data = qFromBigEndian<quint32>(data); |
88 | |
89 | Protocol::Type type = static_cast<Protocol::Type>(data & 0xff); |
90 | quint16 protoFeatures = static_cast<quint16>(data>>8 & 0xffff); |
91 | _supportedProtos.append(PeerFactory::ProtoDescriptor(type, protoFeatures)); |
92 | |
93 | if (data >= 0x80000000) { // last protocol |
94 | Compressor::CompressionLevel level; |
95 | if (_connectionFeatures & Protocol::Compression) |
96 | level = Compressor::BestCompression; |
97 | else |
98 | level = Compressor::NoCompression; |
99 | |
100 | RemotePeer *peer = PeerFactory::createPeer(_supportedProtos, this, socket(), level, this); |
101 | if (peer->protocol() == Protocol::LegacyProtocol) { |
102 | _legacy = true; |
103 | connect(peer, SIGNAL(protocolVersionMismatch(int,int)), SLOT(onProtocolVersionMismatch(int,int))); |
104 | } |
105 | setPeer(peer); |
106 | |
107 | // inform the client |
108 | quint32 reply = peer->protocol() | peer->enabledFeatures()<<8 | _connectionFeatures<<24; |
109 | reply = qToBigEndian<quint32>(reply); |
110 | socket()->write((char*)&reply, 4); |
111 | socket()->flush(); |
112 | |
113 | if (!_legacy && (_connectionFeatures & Protocol::Encryption)) |
114 | startSsl(); // legacy peer enables it later |
115 | return; |
116 | } |
117 | } |
118 | } |
119 | |
120 | |
121 | void CoreAuthHandler::setPeer(RemotePeer *peer) |
122 | { |
123 | qDebug().nospace() << "Using " << qPrintable(peer->protocolName()) << "..." ; |
124 | |
125 | _peer = peer; |
126 | disconnect(socket(), SIGNAL(readyRead()), this, SLOT(onReadyRead())); |
127 | } |
128 | |
129 | // only in compat mode |
130 | void CoreAuthHandler::onProtocolVersionMismatch(int actual, int expected) |
131 | { |
132 | qWarning() << qPrintable(tr("Client" )) << _peer->description() << qPrintable(tr("too old, rejecting." )); |
133 | QString errorString = tr("<b>Your Quassel Client is too old!</b><br>" |
134 | "This core needs at least client/core protocol version %1 (got: %2).<br>" |
135 | "Please consider upgrading your client." ).arg(expected, actual); |
136 | _peer->dispatch(ClientDenied(errorString)); |
137 | _peer->close(); |
138 | } |
139 | |
140 | |
141 | bool CoreAuthHandler::checkClientRegistered() |
142 | { |
143 | if (!_clientRegistered) { |
144 | qWarning() << qPrintable(tr("Client" )) << qPrintable(socket()->peerAddress().toString()) << qPrintable(tr("did not send a registration message before trying to login, rejecting." )); |
145 | _peer->dispatch(ClientDenied(tr("<b>Client not initialized!</b><br>You need to send a registration message before trying to login." ))); |
146 | _peer->close(); |
147 | return false; |
148 | } |
149 | return true; |
150 | } |
151 | |
152 | |
153 | void CoreAuthHandler::handle(const RegisterClient &msg) |
154 | { |
155 | bool useSsl; |
156 | if (_legacy) |
157 | useSsl = Core::sslSupported() && msg.sslSupported; |
158 | else |
159 | useSsl = _connectionFeatures & Protocol::Encryption; |
160 | |
161 | if (Quassel::isOptionSet("require-ssl" ) && !useSsl) { |
162 | _peer->dispatch(ClientDenied(tr("<b>SSL is required!</b><br>You need to use SSL in order to connect to this core." ))); |
163 | _peer->close(); |
164 | return; |
165 | } |
166 | |
167 | QVariantList backends; |
168 | bool configured = Core::isConfigured(); |
169 | if (!configured) |
170 | backends = Core::backendInfo(); |
171 | |
172 | // useSsl and startTime are only used for the legacy protocol |
173 | _peer->dispatch(ClientRegistered(Quassel::features(), configured, backends, useSsl, Core::instance()->startTime())); |
174 | |
175 | if (_legacy && useSsl) |
176 | startSsl(); |
177 | |
178 | _clientRegistered = true; |
179 | } |
180 | |
181 | |
182 | void CoreAuthHandler::handle(const SetupData &msg) |
183 | { |
184 | if (!checkClientRegistered()) |
185 | return; |
186 | |
187 | QString result = Core::setup(msg.adminUser, msg.adminPassword, msg.backend, msg.setupData); |
188 | if (!result.isEmpty()) |
189 | _peer->dispatch(SetupFailed(result)); |
190 | else |
191 | _peer->dispatch(SetupDone()); |
192 | } |
193 | |
194 | |
195 | void CoreAuthHandler::handle(const Login &msg) |
196 | { |
197 | if (!checkClientRegistered()) |
198 | return; |
199 | |
200 | UserId uid = Core::validateUser(msg.user, msg.password); |
201 | if (uid == 0) { |
202 | _peer->dispatch(LoginFailed(tr("<b>Invalid username or password!</b><br>The username/password combination you supplied could not be found in the database." ))); |
203 | return; |
204 | } |
205 | _peer->dispatch(LoginSuccess()); |
206 | |
207 | quInfo() << qPrintable(tr("Client %1 initialized and authenticated successfully as \"%2\" (UserId: %3)." ).arg(socket()->peerAddress().toString(), msg.user, QString::number(uid.toInt()))); |
208 | |
209 | disconnect(socket(), 0, this, 0); |
210 | disconnect(_peer, 0, this, 0); |
211 | _peer->setParent(0); // Core needs to take care of this one now! |
212 | |
213 | socket()->flush(); // Make sure all data is sent before handing over the peer (and socket) to the session thread (bug 682) |
214 | emit handshakeComplete(_peer, uid); |
215 | } |
216 | |
217 | |
218 | /*** SSL Stuff ***/ |
219 | |
220 | void CoreAuthHandler::startSsl() |
221 | { |
222 | #ifdef HAVE_SSL |
223 | QSslSocket *sslSocket = qobject_cast<QSslSocket *>(socket()); |
224 | Q_ASSERT(sslSocket); |
225 | |
226 | qDebug() << qPrintable(tr("Starting encryption for Client:" )) << _peer->description(); |
227 | connect(sslSocket, SIGNAL(sslErrors(const QList<QSslError> &)), SLOT(onSslErrors())); |
228 | sslSocket->flush(); // ensure that the write cache is flushed before we switch to ssl (bug 682) |
229 | sslSocket->startServerEncryption(); |
230 | #endif /* HAVE_SSL */ |
231 | } |
232 | |
233 | |
234 | #ifdef HAVE_SSL |
235 | void CoreAuthHandler::onSslErrors() |
236 | { |
237 | QSslSocket *sslSocket = qobject_cast<QSslSocket *>(socket()); |
238 | Q_ASSERT(sslSocket); |
239 | sslSocket->ignoreSslErrors(); |
240 | } |
241 | #endif |
242 | |
243 | |