set SO_KEEPALIVE on client sockets
[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     socket()->setSocketOption(QAbstractSocket::KeepAliveOption, true);
148
149     _peer = new LegacyPeer(this, socket(), this);
150
151     connect(_peer, SIGNAL(transferProgress(int,int)), SIGNAL(transferProgress(int,int)));
152
153     // compat only
154     connect(_peer, SIGNAL(protocolVersionMismatch(int,int)), SLOT(onProtocolVersionMismatch(int,int)));
155
156     emit statusMessage(tr("Synchronizing to core..."));
157
158     bool useSsl = false;
159 #ifdef HAVE_SSL
160     useSsl = _account.useSsl();
161 #endif
162
163     _peer->dispatch(RegisterClient(Quassel::buildInfo().fancyVersionString, useSsl));
164 }
165
166
167 void ClientAuthHandler::onProtocolVersionMismatch(int actual, int expected)
168 {
169     emit errorPopup(tr("<b>The Quassel Core you are trying to connect to is too old!</b><br>"
170            "We need at least protocol v%1, but the core speaks v%2 only.").arg(expected, actual));
171     requestDisconnect(tr("Incompatible protocol version, connection to core refused"));
172 }
173
174
175 void ClientAuthHandler::handle(const ClientDenied &msg)
176 {
177     emit errorPopup(msg.errorString);
178     requestDisconnect(tr("The core refused connection from this client"));
179 }
180
181
182 void ClientAuthHandler::handle(const ClientRegistered &msg)
183 {
184     _coreConfigured = msg.coreConfigured;
185     _backendInfo = msg.backendInfo;
186
187     Client::setCoreFeatures(static_cast<Quassel::Features>(msg.coreFeatures));
188
189 #ifdef HAVE_SSL
190     CoreAccountSettings s;
191     if (_account.useSsl()) {
192         if (msg.sslSupported) {
193             // Make sure the warning is shown next time we don't have SSL in the core
194             s.setAccountValue("ShowNoCoreSslWarning", true);
195
196             QSslSocket *sslSocket = qobject_cast<QSslSocket *>(socket());
197             Q_ASSERT(sslSocket);
198             connect(sslSocket, SIGNAL(encrypted()), SLOT(onSslSocketEncrypted()));
199             connect(sslSocket, SIGNAL(sslErrors(QList<QSslError>)), SLOT(onSslErrors()));
200             qDebug() << "Starting encryption...";
201             sslSocket->flush();
202             sslSocket->startClientEncryption();
203         }
204         else {
205             if (s.accountValue("ShowNoCoreSslWarning", true).toBool()) {
206                 bool accepted = false;
207                 emit handleNoSslInCore(&accepted);
208                 if (!accepted) {
209                     requestDisconnect(tr("Unencrypted connection cancelled"));
210                     return;
211                 }
212                 s.setAccountValue("ShowNoCoreSslWarning", false);
213                 s.setAccountValue("SslCert", QString());
214             }
215             onConnectionReady();
216         }
217         return;
218     }
219 #endif
220     // if we use SSL we wait for the next step until every SSL warning has been cleared
221     onConnectionReady();
222 }
223
224
225 #ifdef HAVE_SSL
226
227 void ClientAuthHandler::onSslSocketEncrypted()
228 {
229     QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
230     Q_ASSERT(socket);
231
232     if (!socket->sslErrors().count()) {
233         // Cert is valid, so we don't want to store it as known
234         // That way, a warning will appear in case it becomes invalid at some point
235         CoreAccountSettings s;
236         s.setAccountValue("SSLCert", QString());
237     }
238
239     emit encrypted(true);
240     onConnectionReady();
241 }
242
243
244 void ClientAuthHandler::onSslErrors()
245 {
246     QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
247     Q_ASSERT(socket);
248
249     CoreAccountSettings s;
250     QByteArray knownDigest = s.accountValue("SslCert").toByteArray();
251
252     if (knownDigest != socket->peerCertificate().digest()) {
253         bool accepted = false;
254         bool permanently = false;
255         emit handleSslErrors(socket, &accepted, &permanently);
256
257         if (!accepted) {
258             requestDisconnect(tr("Unencrypted connection canceled"));
259             return;
260         }
261
262         if (permanently)
263             s.setAccountValue("SslCert", socket->peerCertificate().digest());
264         else
265             s.setAccountValue("SslCert", QString());
266     }
267
268     socket->ignoreSslErrors();
269 }
270
271 #endif /* HAVE_SSL */
272
273
274 void ClientAuthHandler::onConnectionReady()
275 {
276     emit connectionReady();
277     emit statusMessage(tr("Connected to %1").arg(_account.accountName()));
278
279     if (!_coreConfigured) {
280         // start wizard
281         emit startCoreSetup(_backendInfo);
282     }
283     else // TODO: check if we need LoginEnabled
284         login();
285 }
286
287
288 void ClientAuthHandler::setupCore(const SetupData &setupData)
289 {
290     _peer->dispatch(setupData);
291 }
292
293
294 void ClientAuthHandler::handle(const SetupFailed &msg)
295 {
296     emit coreSetupFailed(msg.errorString);
297 }
298
299
300 void ClientAuthHandler::handle(const SetupDone &msg)
301 {
302     Q_UNUSED(msg)
303
304     emit coreSetupSuccessful();
305 }
306
307
308 void ClientAuthHandler::login(const QString &user, const QString &password, bool remember)
309 {
310     _account.setUser(user);
311     _account.setPassword(password);
312     _account.setStorePassword(remember);
313     login();
314 }
315
316
317 void ClientAuthHandler::login(const QString &previousError)
318 {
319     emit statusMessage(tr("Logging in..."));
320     if (_account.user().isEmpty() || _account.password().isEmpty() || !previousError.isEmpty()) {
321         bool valid = false;
322         emit userAuthenticationRequired(&_account, &valid, previousError); // *must* be a synchronous call
323         if (!valid || _account.user().isEmpty() || _account.password().isEmpty()) {
324             requestDisconnect(tr("Login canceled"));
325             return;
326         }
327     }
328
329     _peer->dispatch(Login(_account.user(), _account.password()));
330 }
331
332
333 void ClientAuthHandler::handle(const LoginFailed &msg)
334 {
335     login(msg.errorString);
336 }
337
338
339 void ClientAuthHandler::handle(const LoginSuccess &msg)
340 {
341     Q_UNUSED(msg)
342
343     emit loginSuccessful(_account);
344 }
345
346
347 void ClientAuthHandler::handle(const SessionState &msg)
348 {
349     disconnect(socket(), 0, this, 0); // this is the last message we shall ever get
350
351     // give up ownership of the peer; CoreSession takes responsibility now
352     _peer->setParent(0);
353     emit handshakeComplete(_peer, msg);
354 }