c74dc4ef05ddb0457f31116edfed82b0ca5ed814
[quassel.git] / src / core / networkconnection.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-08 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  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19  ***************************************************************************/
20 #include "networkconnection.h"
21
22 #include <QMetaObject>
23 #include <QMetaMethod>
24 #include <QDateTime>
25
26 #include "util.h"
27 #include "core.h"
28 #include "coresession.h"
29
30 #include "ircchannel.h"
31 #include "ircuser.h"
32 #include "network.h"
33 #include "identity.h"
34
35 #include "ircserverhandler.h"
36 #include "userinputhandler.h"
37 #include "ctcphandler.h"
38
39 NetworkConnection::NetworkConnection(Network *network, CoreSession *session)
40   : QObject(network),
41     _connectionState(Network::Disconnected),
42     _network(network),
43     _coreSession(session),
44     _ircServerHandler(new IrcServerHandler(this)),
45     _userInputHandler(new UserInputHandler(this)),
46     _ctcpHandler(new CtcpHandler(this)),
47     _autoReconnectCount(0),
48     _quitRequested(false),
49
50     _previousConnectionAttemptFailed(false),
51     _lastUsedServerlistIndex(0),
52
53     // TODO make autowho configurable (possibly per-network)
54     _autoWhoEnabled(true),
55     _autoWhoInterval(90),
56     _autoWhoNickLimit(0), // unlimited
57     _autoWhoDelay(3),
58
59     // TokenBucket to avaid sending too much at once
60     _messagesPerSecond(1),
61     _burstSize(5),
62     _tokenBucket(5), // init with a full bucket
63
64     // TODO: 
65     // should be 510 (2 bytes are added when writing to the socket)
66     // maxMsgSize is 510 minus the hostmask which will be added by the server
67     _maxMsgSize(450)
68 {
69   _autoReconnectTimer.setSingleShot(true);
70   _socketCloseTimer.setSingleShot(true);
71   connect(&_socketCloseTimer, SIGNAL(timeout()), this, SLOT(socketCloseTimeout()));
72   
73   _autoWhoTimer.setInterval(_autoWhoDelay * 1000);
74   _autoWhoCycleTimer.setInterval(_autoWhoInterval * 1000);
75   
76   _tokenBucketTimer.start(_messagesPerSecond * 1000);
77
78   QHash<QString, QString> channels = coreSession()->persistentChannels(networkId());
79   foreach(QString chan, channels.keys()) {
80     _channelKeys[chan.toLower()] = channels[chan];
81   }
82
83   connect(&_autoReconnectTimer, SIGNAL(timeout()), this, SLOT(doAutoReconnect()));
84   connect(&_autoWhoTimer, SIGNAL(timeout()), this, SLOT(sendAutoWho()));
85   connect(&_autoWhoCycleTimer, SIGNAL(timeout()), this, SLOT(startAutoWhoCycle()));
86   connect(&_tokenBucketTimer, SIGNAL(timeout()), this, SLOT(fillBucketAndProcessQueue()));
87
88   connect(network, SIGNAL(currentServerSet(const QString &)), this, SLOT(networkInitialized(const QString &)));
89   connect(network, SIGNAL(useAutoReconnectSet(bool)), this, SLOT(autoReconnectSettingsChanged()));
90   connect(network, SIGNAL(autoReconnectIntervalSet(quint32)), this, SLOT(autoReconnectSettingsChanged()));
91   connect(network, SIGNAL(autoReconnectRetriesSet(quint16)), this, SLOT(autoReconnectSettingsChanged()));
92
93 #ifndef QT_NO_OPENSSL
94   {
95     QFile certFile(quasselDir().absolutePath() + "/quasselClientCert.pem");
96     certFile.open(QIODevice::ReadOnly);
97     QSslCertificate cert(&certFile);
98     certFile.close();
99
100     certFile.open(QIODevice::ReadOnly);
101     QSslKey key(&certFile, QSsl::Rsa);
102     certFile.close();
103
104     if ( !cert.isNull() && cert.isValid() &&
105          !key.isNull() ) {
106       socket.setLocalCertificate(cert);
107       socket.setPrivateKey(key);
108     }
109   }
110
111   connect(&socket, SIGNAL(encrypted()), this, SLOT(socketEncrypted()));
112   connect(&socket, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(sslErrors(const QList<QSslError> &)));
113 #endif
114   connect(&socket, SIGNAL(connected()), this, SLOT(socketConnected()));
115
116   connect(&socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected()));
117   connect(&socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError)));
118   connect(&socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(socketStateChanged(QAbstractSocket::SocketState)));
119   connect(&socket, SIGNAL(readyRead()), this, SLOT(socketHasData()));
120
121   connect(_ircServerHandler, SIGNAL(nickChanged(const QString &, const QString &)),
122           this, SLOT(nickChanged(const QString &, const QString &)));
123
124   network->proxy()->attachSignal(this, SIGNAL(sslErrors(const QVariant &)));
125 }
126
127 NetworkConnection::~NetworkConnection() {
128   if(connectionState() != Network::Disconnected && connectionState() != Network::Reconnecting)
129     disconnectFromIrc(false); // clean up, but this does not count as requested disconnect!
130   disconnect(&socket, 0, this, 0); // this keeps the socket from triggering events during clean up
131   delete _ircServerHandler;
132   delete _userInputHandler;
133   delete _ctcpHandler;
134 }
135
136 void NetworkConnection::setConnectionState(Network::ConnectionState state) {
137   _connectionState = state;
138   network()->setConnectionState(state);
139   emit connectionStateChanged(state);
140 }
141
142 QString NetworkConnection::serverDecode(const QByteArray &string) const {
143   return network()->decodeServerString(string);
144 }
145
146 QString NetworkConnection::channelDecode(const QString &bufferName, const QByteArray &string) const {
147   if(!bufferName.isEmpty()) {
148     IrcChannel *channel = network()->ircChannel(bufferName);
149     if(channel) return channel->decodeString(string);
150   }
151   return network()->decodeString(string);
152 }
153
154 QString NetworkConnection::userDecode(const QString &userNick, const QByteArray &string) const {
155   IrcUser *user = network()->ircUser(userNick);
156   if(user) return user->decodeString(string);
157   return network()->decodeString(string);
158 }
159
160 QByteArray NetworkConnection::serverEncode(const QString &string) const {
161   return network()->encodeServerString(string);
162 }
163
164 QByteArray NetworkConnection::channelEncode(const QString &bufferName, const QString &string) const {
165   if(!bufferName.isEmpty()) {
166     IrcChannel *channel = network()->ircChannel(bufferName);
167     if(channel) return channel->encodeString(string);
168   }
169   return network()->encodeString(string);
170 }
171
172 QByteArray NetworkConnection::userEncode(const QString &userNick, const QString &string) const {
173   IrcUser *user = network()->ircUser(userNick);
174   if(user) return user->encodeString(string);
175   return network()->encodeString(string);
176 }
177
178 void NetworkConnection::autoReconnectSettingsChanged() {
179   if(!network()->useAutoReconnect()) {
180     _autoReconnectTimer.stop();
181     _autoReconnectCount = 0;
182   } else {
183     _autoReconnectTimer.setInterval(network()->autoReconnectInterval() * 1000);
184     if(_autoReconnectCount != 0) {
185       if(network()->unlimitedReconnectRetries()) _autoReconnectCount = -1;
186       else _autoReconnectCount = network()->autoReconnectRetries();
187     }
188   }
189 }
190
191 void NetworkConnection::connectToIrc(bool reconnecting) {
192   if(!reconnecting && network()->useAutoReconnect() && _autoReconnectCount == 0) {
193     _autoReconnectTimer.setInterval(network()->autoReconnectInterval() * 1000);
194     if(network()->unlimitedReconnectRetries()) _autoReconnectCount = -1;
195     else _autoReconnectCount = network()->autoReconnectRetries();
196   }
197   QVariantList serverList = network()->serverList();
198   Identity *identity = coreSession()->identity(network()->identity());
199   if(!serverList.count()) {
200     qWarning() << "Server list empty, ignoring connect request!";
201     return;
202   }
203   if(!identity) {
204     qWarning() << "Invalid identity configures, ignoring connect request!";
205     return;
206   }
207   // use a random server?
208   if(network()->useRandomServer()) {
209     _lastUsedServerlistIndex = qrand() % serverList.size();
210   } else if(_previousConnectionAttemptFailed) {
211     // cycle to next server if previous connection attempt failed
212     displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Connection failed. Cycling to next Server"));
213     if(++_lastUsedServerlistIndex == serverList.size()) {
214       _lastUsedServerlistIndex = 0;
215     }
216   }
217   _previousConnectionAttemptFailed = false;
218
219   QString host = serverList[_lastUsedServerlistIndex].toMap()["Host"].toString();
220   quint16 port = serverList[_lastUsedServerlistIndex].toMap()["Port"].toUInt();
221   displayStatusMsg(tr("Connecting to %1:%2...").arg(host).arg(port));
222   displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Connecting to %1:%2...").arg(host).arg(port));
223   socket.connectToHost(host, port);
224 }
225
226 void NetworkConnection::networkInitialized(const QString &currentServer) {
227   if(currentServer.isEmpty()) return;
228
229   if(network()->useAutoReconnect() && !network()->unlimitedReconnectRetries()) {
230     _autoReconnectCount = network()->autoReconnectRetries(); // reset counter
231   }
232
233   sendPerform();
234
235   // now we are initialized
236   setConnectionState(Network::Initialized);
237   network()->setConnected(true);
238   emit connected(networkId());
239
240   if(_autoWhoEnabled) {
241     _autoWhoCycleTimer.start();
242     _autoWhoTimer.start();
243     startAutoWhoCycle();  // FIXME wait for autojoin to be completed
244   }
245 }
246
247 void NetworkConnection::sendPerform() {
248   BufferInfo statusBuf = Core::bufferInfo(coreSession()->user(), network()->networkId(), BufferInfo::StatusBuffer);
249   // do auto identify
250   if(network()->useAutoIdentify() && !network()->autoIdentifyService().isEmpty() && !network()->autoIdentifyPassword().isEmpty()) {
251     userInputHandler()->handleMsg(statusBuf, QString("%1 IDENTIFY %2").arg(network()->autoIdentifyService(), network()->autoIdentifyPassword()));
252   }
253   // send perform list
254   foreach(QString line, network()->perform()) {
255     if(!line.isEmpty()) userInput(statusBuf, line);
256   }
257
258   // rejoin channels we've been in
259   QStringList channels, keys;
260   foreach(QString chan, persistentChannels()) {
261     QString key = channelKey(chan);
262     if(!key.isEmpty()) {
263       channels.prepend(chan); keys.prepend(key);
264     } else {
265       channels.append(chan);
266     }
267   }
268   QString joinString = QString("%1 %2").arg(channels.join(",")).arg(keys.join(",")).trimmed();
269   if(!joinString.isEmpty()) userInputHandler()->handleJoin(statusBuf, joinString);
270 }
271
272 void NetworkConnection::disconnectFromIrc(bool requested) {
273   _autoReconnectTimer.stop();
274   _autoReconnectCount = 0;
275   displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Disconnecting."));
276   if(socket.state() < QAbstractSocket::ConnectedState) {
277     setConnectionState(Network::Disconnected);
278     socketDisconnected();
279   } else {
280     _socketCloseTimer.start(10000); // the irc server has 10 seconds to close the socket
281   }
282
283   // this flag triggers quitRequested() once the socket is closed
284   // it is needed to determine whether or not the connection needs to be
285   // in the automatic session restore.
286   _quitRequested = requested;
287 }
288
289 void NetworkConnection::socketHasData() {
290   while(socket.canReadLine()) {
291     QByteArray s = socket.readLine().trimmed();
292     ircServerHandler()->handleServerMsg(s);
293   }
294 }
295
296 void NetworkConnection::socketError(QAbstractSocket::SocketError) {
297   _previousConnectionAttemptFailed = true;
298   qDebug() << qPrintable(tr("Could not connect to %1 (%2)").arg(network()->networkName(), socket.errorString()));
299   emit connectionError(socket.errorString());
300   emit displayMsg(Message::Error, BufferInfo::StatusBuffer, "", tr("Connection failure: %1").arg(socket.errorString()));
301   network()->emitConnectionError(socket.errorString());
302   if(socket.state() < QAbstractSocket::ConnectedState) {
303     setConnectionState(Network::Disconnected);
304     socketDisconnected();
305   }
306   // mark last connection attempt as failed
307   
308   //qDebug() << "exiting...";
309   //exit(1);
310 }
311
312 #ifndef QT_NO_OPENSSL
313
314 void NetworkConnection::sslErrors(const QList<QSslError> &sslErrors) {
315   Q_UNUSED(sslErrors)
316   socket.ignoreSslErrors();
317   /* TODO errorhandling
318   QVariantMap errmsg;
319   QVariantList errnums;
320   foreach(QSslError err, errors) errnums << err.error();
321   errmsg["SslErrors"] = errnums;
322   errmsg["SslCert"] = socket.peerCertificate().toPem();
323   errmsg["PeerAddress"] = socket.peerAddress().toString();
324   errmsg["PeerPort"] = socket.peerPort();
325   errmsg["PeerName"] = socket.peerName();
326   emit sslErrors(errmsg);
327   disconnectFromIrc();
328   */
329 }
330
331 void NetworkConnection::socketEncrypted() {
332   //qDebug() << "encrypted!";
333   socketInitialized();
334 }
335
336 #endif  // QT_NO_OPENSSL
337
338 void NetworkConnection::socketConnected() {
339 #ifdef QT_NO_OPENSSL
340   socketInitialized();
341   return;
342 #else
343   if(!network()->serverList()[_lastUsedServerlistIndex].toMap()["UseSSL"].toBool()) {
344     socketInitialized();
345     return;
346   }
347   //qDebug() << "starting handshake";
348   socket.startClientEncryption();
349 #endif
350 }
351
352 void NetworkConnection::socketInitialized() {
353   //emit connected(networkId());  initialize first!
354   Identity *identity = coreSession()->identity(network()->identity());
355   if(!identity) {
356     qWarning() << "Identity invalid!";
357     disconnectFromIrc();
358     return;
359   }
360   QString passwd = network()->serverList()[_lastUsedServerlistIndex].toMap()["Password"].toString();
361   if(!passwd.isEmpty()) {
362     putRawLine(serverEncode(QString("PASS %1").arg(passwd)));
363   }
364   putRawLine(serverEncode(QString("NICK :%1").arg(identity->nicks()[0])));  // FIXME: try more nicks if error occurs
365   putRawLine(serverEncode(QString("USER %1 8 * :%2").arg(identity->ident(), identity->realName())));
366 }
367
368 void NetworkConnection::socketStateChanged(QAbstractSocket::SocketState socketState) {
369   Network::ConnectionState state;
370   switch(socketState) {
371     case QAbstractSocket::UnconnectedState:
372       state = Network::Disconnected;
373       break;
374     case QAbstractSocket::HostLookupState:
375     case QAbstractSocket::ConnectingState:
376       state = Network::Connecting;
377       break;
378     case QAbstractSocket::ConnectedState:
379       state = Network::Initializing;
380       break;
381     case QAbstractSocket::ClosingState:
382       state = Network::Disconnecting;
383       break;
384     default:
385       state = Network::Disconnected;
386   }
387   setConnectionState(state);
388 }
389
390 void NetworkConnection::socketCloseTimeout() {
391   socket.disconnectFromHost();
392 }
393
394 void NetworkConnection::socketDisconnected() {
395   _autoWhoCycleTimer.stop();
396   _autoWhoTimer.stop();
397   _autoWhoQueue.clear();
398   _autoWhoInProgress.clear();
399
400   _socketCloseTimer.stop();
401   
402   network()->setConnected(false);
403   emit disconnected(networkId());
404   if(_autoReconnectCount != 0) {
405     setConnectionState(Network::Reconnecting);
406     if(_autoReconnectCount == network()->autoReconnectRetries()) doAutoReconnect(); // first try is immediate
407     else _autoReconnectTimer.start();
408   } else if(_quitRequested) {
409     emit quitRequested(networkId());
410   }
411 }
412
413 void NetworkConnection::doAutoReconnect() {
414   if(connectionState() != Network::Disconnected && connectionState() != Network::Reconnecting) {
415     qWarning() << "NetworkConnection::doAutoReconnect(): Cannot reconnect while not being disconnected!";
416     return;
417   }
418   if(_autoReconnectCount > 0) _autoReconnectCount--;
419   connectToIrc(true);
420 }
421
422 // FIXME switch to BufferId
423 void NetworkConnection::userInput(BufferInfo buf, QString msg) {
424   userInputHandler()->handleUserInput(buf, msg);
425 }
426
427 void NetworkConnection::putRawLine(QByteArray s) {
428   if(_tokenBucket > 0) {
429     // qDebug() << "putRawLine: " << s;
430     writeToSocket(s);
431   } else {
432     _msgQueue.append(s);
433   }
434 }
435
436 void NetworkConnection::writeToSocket(QByteArray s) {
437   s += "\r\n";
438   // qDebug() << "writeToSocket: " << s.size();
439   socket.write(s);
440   _tokenBucket--;
441 }
442
443 void NetworkConnection::fillBucketAndProcessQueue() {
444   if(_tokenBucket < _burstSize) {
445     _tokenBucket++;
446   }
447
448   while(_msgQueue.size() > 0 && _tokenBucket > 0) {
449     writeToSocket(_msgQueue.takeFirst());
450   }
451 }
452
453 void NetworkConnection::putCmd(const QString &cmd, const QVariantList &params, const QByteArray &prefix) {
454   QByteArray msg;
455   if(!prefix.isEmpty())
456     msg += ":" + prefix + " ";
457   msg += cmd.toUpper().toAscii();
458
459   for(int i = 0; i < params.size() - 1; i++) {
460     msg += " " + params[i].toByteArray();
461   }
462   if(!params.isEmpty())
463     msg += " :" + params.last().toByteArray();
464
465   if(cmd == "PRIVMSG" && params.count() > 1) {
466     QByteArray msghead = "PRIVMSG " + params[0].toByteArray() + " :";
467
468     while (msg.size() > _maxMsgSize) {
469       QByteArray splitter(" .,-");
470       int splitPosition = 0;
471       for(int i = 0; i < splitter.size(); i++) {
472         splitPosition = qMax(splitPosition, msg.lastIndexOf(splitter[i], _maxMsgSize));
473       }
474       if(splitPosition < 300) {
475         splitPosition = _maxMsgSize;
476       }
477       putRawLine(msg.left(splitPosition)); 
478       msg = msghead + msg.mid(splitPosition);
479     }
480   }
481
482   putRawLine(msg);
483 }
484
485 void NetworkConnection::sendAutoWho() {
486   while(!_autoWhoQueue.isEmpty()) {
487     QString chan = _autoWhoQueue.takeFirst();
488     IrcChannel *ircchan = network()->ircChannel(chan);
489     if(!ircchan) continue;
490     if(_autoWhoNickLimit > 0 && ircchan->ircUsers().count() > _autoWhoNickLimit) continue;
491     _autoWhoInProgress[chan]++;
492     putRawLine("WHO " + serverEncode(chan));
493     if(_autoWhoQueue.isEmpty() && _autoWhoEnabled && !_autoWhoCycleTimer.isActive()) {
494       // Timer was stopped, means a new cycle is due immediately
495       _autoWhoCycleTimer.start();
496       startAutoWhoCycle();
497     }
498     break;
499   }
500 }
501
502 void NetworkConnection::startAutoWhoCycle() {
503   if(!_autoWhoQueue.isEmpty()) {
504     _autoWhoCycleTimer.stop();
505     return;
506   }
507   _autoWhoQueue = network()->channels();
508 }
509
510 bool NetworkConnection::setAutoWhoDone(const QString &channel) {
511   if(_autoWhoInProgress.value(channel.toLower(), 0) <= 0) return false;
512   _autoWhoInProgress[channel.toLower()]--;
513   return true;
514 }
515
516 void NetworkConnection::setChannelJoined(const QString &channel) {
517   emit channelJoined(networkId(), channel, _channelKeys[channel.toLower()]);
518   _autoWhoQueue.prepend(channel.toLower()); // prepend so this new chan is the first to be checked
519 }
520
521 void NetworkConnection::setChannelParted(const QString &channel) {
522   removeChannelKey(channel);
523   _autoWhoQueue.removeAll(channel.toLower());
524   _autoWhoInProgress.remove(channel.toLower());
525   emit channelParted(networkId(), channel);
526 }
527
528 void NetworkConnection::addChannelKey(const QString &channel, const QString &key) {
529   if(key.isEmpty()) {
530     removeChannelKey(channel);
531   } else {
532     _channelKeys[channel.toLower()] = key;
533   }
534 }
535
536 void NetworkConnection::removeChannelKey(const QString &channel) {
537   _channelKeys.remove(channel.toLower());
538 }
539
540 void NetworkConnection::nickChanged(const QString &newNick, const QString &oldNick) {
541   emit nickChanged(networkId(), newNick, oldNick);
542 }
543
544 /* Exception classes for message handling */
545 NetworkConnection::ParseError::ParseError(QString cmd, QString prefix, QStringList params) {
546   Q_UNUSED(prefix);
547   _msg = QString("Command Parse Error: ") + cmd + params.join(" ");
548 }
549
550 NetworkConnection::UnknownCmdError::UnknownCmdError(QString cmd, QString prefix, QStringList params) {
551   Q_UNUSED(prefix);
552   _msg = QString("Unknown Command: ") + cmd + params.join(" ");
553 }
554