Happy New Year!
[quassel.git] / src / client / clientauthhandler.cpp
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 "clientauthhandler.h"
22
23 // TODO: support system application proxy (new in Qt 4.6)
24
25 #ifdef HAVE_SSL
26     #include <QSslSocket>
27 #else
28     #include <QTcpSocket>
29 #endif
30
31 #include "client.h"
32 #include "clientsettings.h"
33
34 #include "protocols/legacy/legacypeer.h"
35
36 using namespace Protocol;
37
38 ClientAuthHandler::ClientAuthHandler(CoreAccount account, QObject *parent)
39     : AuthHandler(parent),
40     _peer(0),
41     _account(account)
42 {
43
44 }
45
46
47 void ClientAuthHandler::connectToCore()
48 {
49     CoreAccountSettings s;
50
51 #ifdef HAVE_SSL
52     QSslSocket *socket = new QSslSocket(this);
53     // make sure the warning is shown if we happen to connect without SSL support later
54     s.setAccountValue("ShowNoClientSslWarning", true);
55 #else
56     if (_account.useSsl()) {
57         if (s.accountValue("ShowNoClientSslWarning", true).toBool()) {
58             bool accepted = false;
59             emit handleNoSslInClient(&accepted);
60             if (!accepted) {
61                 emit errorMessage(tr("Unencrypted connection canceled"));
62                 return;
63             }
64             s.setAccountValue("ShowNoClientSslWarning", false);
65         }
66     }
67     QTcpSocket *socket = new QTcpSocket(this);
68 #endif
69
70 // TODO: Handle system proxy
71 #ifndef QT_NO_NETWORKPROXY
72     if (_account.useProxy()) {
73         QNetworkProxy proxy(_account.proxyType(), _account.proxyHostName(), _account.proxyPort(), _account.proxyUser(), _account.proxyPassword());
74         socket->setProxy(proxy);
75     }
76 #endif
77
78     setSocket(socket);
79     // handled by the base class for now; may need to rethink for protocol detection
80     //connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(onSocketError(QAbstractSocket::SocketError)));
81     //connect(socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), SLOT(onSocketStateChanged(QAbstractSocket::SocketState)));
82     connect(socket, SIGNAL(connected()), SLOT(onSocketConnected()));
83
84     emit statusMessage(tr("Connecting to %1...").arg(_account.accountName()));
85     socket->connectToHost(_account.hostName(), _account.port());
86 }
87
88
89 // TODO: handle protocol detection
90 // This method might go away anyway, unless we really need our own states...
91 /*
92 void ClientAuthHandler::onSocketStateChanged(QAbstractSocket::SocketState socketState)
93 {
94     qDebug() << Q_FUNC_INFO << socketState;
95     QString text;
96     AuthHandler::State state = UnconnectedState;
97
98     switch(socketState) {
99         case QAbstractSocket::UnconnectedState:
100             text = tr("Disconnected");
101             state = UnconnectedState;
102             break;
103         case QAbstractSocket::HostLookupState:
104             text = tr("Looking up %1...").arg(_account.hostName());
105             state = HostLookupState;
106             break;
107         case QAbstractSocket::ConnectingState:
108             text = tr("Connecting to %1...").arg(_account.hostName());
109             state = ConnectingState;
110             break;
111         case QAbstractSocket::ConnectedState:
112             text = tr("Connected to %1").arg(_account.hostName());
113             state = ConnectedState;
114             break;
115         case QAbstractSocket::ClosingState:
116             text = tr("Disconnecting from %1...").arg(_account.hostName());
117             state = ClosingState;
118             break;
119         default:
120             break;
121     }
122
123     if (!text.isEmpty()) {
124         setState(state);
125         emit statusMessage(text);
126     }
127 }
128 */
129
130 // TODO: handle protocol detection
131 /*
132 void ClientAuthHandler::onSocketError(QAbstractSocket::SocketError error)
133 {
134     emit socketError(error, socket()->errorString());
135 }
136 */
137
138 void ClientAuthHandler::onSocketConnected()
139 {
140     // TODO: protocol detection
141
142     if (_peer) {
143         qWarning() << Q_FUNC_INFO << "Peer already exists!";
144         return;
145     }
146
147     _peer = new LegacyPeer(this, socket(), this);
148
149     connect(_peer, SIGNAL(transferProgress(int,int)), SIGNAL(transferProgress(int,int)));
150
151     // compat only
152     connect(_peer, SIGNAL(protocolVersionMismatch(int,int)), SLOT(onProtocolVersionMismatch(int,int)));
153
154     emit statusMessage(tr("Synchronizing to core..."));
155
156     bool useSsl = false;
157 #ifdef HAVE_SSL
158     useSsl = _account.useSsl();
159 #endif
160
161     _peer->dispatch(RegisterClient(Quassel::buildInfo().fancyVersionString, useSsl));
162 }
163
164
165 void ClientAuthHandler::onProtocolVersionMismatch(int actual, int expected)
166 {
167     emit errorPopup(tr("<b>The Quassel Core you are trying to connect to is too old!</b><br>"
168            "We need at least protocol v%1, but the core speaks v%2 only.").arg(expected, actual));
169     requestDisconnect(tr("Incompatible protocol version, connection to core refused"));
170 }
171
172
173 void ClientAuthHandler::handle(const ClientDenied &msg)
174 {
175     emit errorPopup(msg.errorString);
176     requestDisconnect(tr("The core refused connection from this client"));
177 }
178
179
180 void ClientAuthHandler::handle(const ClientRegistered &msg)
181 {
182     _coreConfigured = msg.coreConfigured;
183     _backendInfo = msg.backendInfo;
184
185     Client::setCoreFeatures(static_cast<Quassel::Features>(msg.coreFeatures));
186
187 #ifdef HAVE_SSL
188     CoreAccountSettings s;
189     if (_account.useSsl()) {
190         if (msg.sslSupported) {
191             // Make sure the warning is shown next time we don't have SSL in the core
192             s.setAccountValue("ShowNoCoreSslWarning", true);
193
194             QSslSocket *sslSocket = qobject_cast<QSslSocket *>(socket());
195             Q_ASSERT(sslSocket);
196             connect(sslSocket, SIGNAL(encrypted()), SLOT(onSslSocketEncrypted()));
197             connect(sslSocket, SIGNAL(sslErrors(QList<QSslError>)), SLOT(onSslErrors()));
198             qDebug() << "Starting encryption...";
199             sslSocket->flush();
200             sslSocket->startClientEncryption();
201         }
202         else {
203             if (s.accountValue("ShowNoCoreSslWarning", true).toBool()) {
204                 bool accepted = false;
205                 emit handleNoSslInCore(&accepted);
206                 if (!accepted) {
207                     requestDisconnect(tr("Unencrypted connection cancelled"));
208                     return;
209                 }
210                 s.setAccountValue("ShowNoCoreSslWarning", false);
211                 s.setAccountValue("SslCert", QString());
212             }
213             onConnectionReady();
214         }
215         return;
216     }
217 #endif
218     // if we use SSL we wait for the next step until every SSL warning has been cleared
219     onConnectionReady();
220 }
221
222
223 #ifdef HAVE_SSL
224
225 void ClientAuthHandler::onSslSocketEncrypted()
226 {
227     QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
228     Q_ASSERT(socket);
229
230     if (!socket->sslErrors().count()) {
231         // Cert is valid, so we don't want to store it as known
232         // That way, a warning will appear in case it becomes invalid at some point
233         CoreAccountSettings s;
234         s.setAccountValue("SSLCert", QString());
235     }
236
237     emit encrypted(true);
238     onConnectionReady();
239 }
240
241
242 void ClientAuthHandler::onSslErrors()
243 {
244     QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
245     Q_ASSERT(socket);
246
247     CoreAccountSettings s;
248     QByteArray knownDigest = s.accountValue("SslCert").toByteArray();
249
250     if (knownDigest != socket->peerCertificate().digest()) {
251         bool accepted = false;
252         bool permanently = false;
253         emit handleSslErrors(socket, &accepted, &permanently);
254
255         if (!accepted) {
256             requestDisconnect(tr("Unencrypted connection canceled"));
257             return;
258         }
259
260         if (permanently)
261             s.setAccountValue("SslCert", socket->peerCertificate().digest());
262         else
263             s.setAccountValue("SslCert", QString());
264     }
265
266     socket->ignoreSslErrors();
267 }
268
269 #endif /* HAVE_SSL */
270
271
272 void ClientAuthHandler::onConnectionReady()
273 {
274     emit connectionReady();
275     emit statusMessage(tr("Connected to %1").arg(_account.accountName()));
276
277     if (!_coreConfigured) {
278         // start wizard
279         emit startCoreSetup(_backendInfo);
280     }
281     else // TODO: check if we need LoginEnabled
282         login();
283 }
284
285
286 void ClientAuthHandler::setupCore(const SetupData &setupData)
287 {
288     _peer->dispatch(setupData);
289 }
290
291
292 void ClientAuthHandler::handle(const SetupFailed &msg)
293 {
294     emit coreSetupFailed(msg.errorString);
295 }
296
297
298 void ClientAuthHandler::handle(const SetupDone &msg)
299 {
300     Q_UNUSED(msg)
301
302     emit coreSetupSuccessful();
303 }
304
305
306 void ClientAuthHandler::login(const QString &user, const QString &password, bool remember)
307 {
308     _account.setUser(user);
309     _account.setPassword(password);
310     _account.setStorePassword(remember);
311     login();
312 }
313
314
315 void ClientAuthHandler::login(const QString &previousError)
316 {
317     emit statusMessage(tr("Logging in..."));
318     if (_account.user().isEmpty() || _account.password().isEmpty() || !previousError.isEmpty()) {
319         bool valid = false;
320         emit userAuthenticationRequired(&_account, &valid, previousError); // *must* be a synchronous call
321         if (!valid || _account.user().isEmpty() || _account.password().isEmpty()) {
322             requestDisconnect(tr("Login canceled"));
323             return;
324         }
325     }
326
327     _peer->dispatch(Login(_account.user(), _account.password()));
328 }
329
330
331 void ClientAuthHandler::handle(const LoginFailed &msg)
332 {
333     login(msg.errorString);
334 }
335
336
337 void ClientAuthHandler::handle(const LoginSuccess &msg)
338 {
339     Q_UNUSED(msg)
340
341     emit loginSuccessful(_account);
342 }
343
344
345 void ClientAuthHandler::handle(const SessionState &msg)
346 {
347     disconnect(socket(), 0, this, 0); // this is the last message we shall ever get
348
349     // give up ownership of the peer; CoreSession takes responsibility now
350     _peer->setParent(0);
351     emit handshakeComplete(_peer, msg);
352 }