core: connectToIrc: Prevent DNS leaks on connection when using proxy
[quassel.git] / src / core / corenetwork.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2015 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 <QHostInfo>
22
23 #include "corenetwork.h"
24
25 #include "core.h"
26 #include "coreidentity.h"
27 #include "corenetworkconfig.h"
28 #include "coresession.h"
29 #include "coreuserinputhandler.h"
30 #include "networkevent.h"
31
32 INIT_SYNCABLE_OBJECT(CoreNetwork)
33 CoreNetwork::CoreNetwork(const NetworkId &networkid, CoreSession *session)
34     : Network(networkid, session),
35     _coreSession(session),
36     _userInputHandler(new CoreUserInputHandler(this)),
37     _autoReconnectCount(0),
38     _quitRequested(false),
39     _disconnectExpected(false),
40
41     _previousConnectionAttemptFailed(false),
42     _lastUsedServerIndex(0),
43
44     _lastPingTime(0),
45     _pingCount(0),
46     _sendPings(false),
47     _requestedUserModes('-')
48 {
49     _autoReconnectTimer.setSingleShot(true);
50     connect(&_socketCloseTimer, SIGNAL(timeout()), this, SLOT(socketCloseTimeout()));
51
52     setPingInterval(networkConfig()->pingInterval());
53     connect(&_pingTimer, SIGNAL(timeout()), this, SLOT(sendPing()));
54
55     setAutoWhoDelay(networkConfig()->autoWhoDelay());
56     setAutoWhoInterval(networkConfig()->autoWhoInterval());
57
58     QHash<QString, QString> channels = coreSession()->persistentChannels(networkId());
59     foreach(QString chan, channels.keys()) {
60         _channelKeys[chan.toLower()] = channels[chan];
61     }
62
63     connect(networkConfig(), SIGNAL(pingTimeoutEnabledSet(bool)), SLOT(enablePingTimeout(bool)));
64     connect(networkConfig(), SIGNAL(pingIntervalSet(int)), SLOT(setPingInterval(int)));
65     connect(networkConfig(), SIGNAL(autoWhoEnabledSet(bool)), SLOT(setAutoWhoEnabled(bool)));
66     connect(networkConfig(), SIGNAL(autoWhoIntervalSet(int)), SLOT(setAutoWhoInterval(int)));
67     connect(networkConfig(), SIGNAL(autoWhoDelaySet(int)), SLOT(setAutoWhoDelay(int)));
68
69     connect(&_autoReconnectTimer, SIGNAL(timeout()), this, SLOT(doAutoReconnect()));
70     connect(&_autoWhoTimer, SIGNAL(timeout()), this, SLOT(sendAutoWho()));
71     connect(&_autoWhoCycleTimer, SIGNAL(timeout()), this, SLOT(startAutoWhoCycle()));
72     connect(&_tokenBucketTimer, SIGNAL(timeout()), this, SLOT(fillBucketAndProcessQueue()));
73
74     connect(&socket, SIGNAL(connected()), this, SLOT(socketInitialized()));
75     connect(&socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError)));
76     connect(&socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(socketStateChanged(QAbstractSocket::SocketState)));
77     connect(&socket, SIGNAL(readyRead()), this, SLOT(socketHasData()));
78 #ifdef HAVE_SSL
79     connect(&socket, SIGNAL(encrypted()), this, SLOT(socketInitialized()));
80     connect(&socket, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(sslErrors(const QList<QSslError> &)));
81 #endif
82     connect(this, SIGNAL(newEvent(Event *)), coreSession()->eventManager(), SLOT(postEvent(Event *)));
83
84     if (Quassel::isOptionSet("oidentd")) {
85         connect(this, SIGNAL(socketInitialized(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)), Core::instance()->oidentdConfigGenerator(), SLOT(addSocket(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)), Qt::BlockingQueuedConnection);
86         connect(this, SIGNAL(socketDisconnected(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)), Core::instance()->oidentdConfigGenerator(), SLOT(removeSocket(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)));
87     }
88 }
89
90
91 CoreNetwork::~CoreNetwork()
92 {
93     // Request a proper disconnect, but don't count as user-requested disconnect
94     if (socketConnected()) {
95         // Only try if the socket's fully connected (not initializing or disconnecting).
96         // Force an immediate disconnect, jumping the command queue.  Ensures the proper QUIT is
97         // shown even if other messages are queued.
98         disconnectFromIrc(false, QString(), false, true);
99         // Process the putCmd events that trigger the quit.  Without this, shutting down the core
100         // results in abrubtly closing the socket rather than sending the QUIT as expected.
101         QCoreApplication::processEvents();
102         // Wait briefly for each network to disconnect.  Sometimes it takes a little while to send.
103         if (!forceDisconnect()) {
104             qWarning() << "Timed out quitting network" << networkName() <<
105                           "(user ID " << userId() << ")";
106         }
107     }
108     disconnect(&socket, 0, this, 0); // this keeps the socket from triggering events during clean up
109     delete _userInputHandler;
110 }
111
112
113 bool CoreNetwork::forceDisconnect(int msecs)
114 {
115     if (socket.state() == QAbstractSocket::UnconnectedState) {
116         // Socket already disconnected.
117         return true;
118     }
119     // Request a socket-level disconnect if not already happened
120     socket.disconnectFromHost();
121     // Return the result of waiting for disconnect; true if successful, otherwise false
122     return socket.waitForDisconnected(msecs);
123 }
124
125
126 QString CoreNetwork::channelDecode(const QString &bufferName, const QByteArray &string) const
127 {
128     if (!bufferName.isEmpty()) {
129         IrcChannel *channel = ircChannel(bufferName);
130         if (channel)
131             return channel->decodeString(string);
132     }
133     return decodeString(string);
134 }
135
136
137 QString CoreNetwork::userDecode(const QString &userNick, const QByteArray &string) const
138 {
139     IrcUser *user = ircUser(userNick);
140     if (user)
141         return user->decodeString(string);
142     return decodeString(string);
143 }
144
145
146 QByteArray CoreNetwork::channelEncode(const QString &bufferName, const QString &string) const
147 {
148     if (!bufferName.isEmpty()) {
149         IrcChannel *channel = ircChannel(bufferName);
150         if (channel)
151             return channel->encodeString(string);
152     }
153     return encodeString(string);
154 }
155
156
157 QByteArray CoreNetwork::userEncode(const QString &userNick, const QString &string) const
158 {
159     IrcUser *user = ircUser(userNick);
160     if (user)
161         return user->encodeString(string);
162     return encodeString(string);
163 }
164
165
166 void CoreNetwork::connectToIrc(bool reconnecting)
167 {
168     if (!reconnecting && useAutoReconnect() && _autoReconnectCount == 0) {
169         _autoReconnectTimer.setInterval(autoReconnectInterval() * 1000);
170         if (unlimitedReconnectRetries())
171             _autoReconnectCount = -1;
172         else
173             _autoReconnectCount = autoReconnectRetries();
174     }
175     if (serverList().isEmpty()) {
176         qWarning() << "Server list empty, ignoring connect request!";
177         return;
178     }
179     CoreIdentity *identity = identityPtr();
180     if (!identity) {
181         qWarning() << "Invalid identity configures, ignoring connect request!";
182         return;
183     }
184
185     // cleaning up old quit reason
186     _quitReason.clear();
187
188     // use a random server?
189     if (useRandomServer()) {
190         _lastUsedServerIndex = qrand() % serverList().size();
191     }
192     else if (_previousConnectionAttemptFailed) {
193         // cycle to next server if previous connection attempt failed
194         _previousConnectionAttemptFailed = false;
195         displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Connection failed. Cycling to next Server"));
196         if (++_lastUsedServerIndex >= serverList().size()) {
197             _lastUsedServerIndex = 0;
198         }
199     }
200     else {
201         // Start out with the top server in the list
202         _lastUsedServerIndex = 0;
203     }
204
205     Server server = usedServer();
206     displayStatusMsg(tr("Connecting to %1:%2...").arg(server.host).arg(server.port));
207     displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Connecting to %1:%2...").arg(server.host).arg(server.port));
208
209     if (server.useProxy) {
210         QNetworkProxy proxy((QNetworkProxy::ProxyType)server.proxyType, server.proxyHost, server.proxyPort, server.proxyUser, server.proxyPass);
211         socket.setProxy(proxy);
212     }
213     else {
214         socket.setProxy(QNetworkProxy::NoProxy);
215     }
216
217     enablePingTimeout();
218
219     // Qt caches DNS entries for a minute, resulting in round-robin (e.g. for chat.freenode.net) not working if several users
220     // connect at a similar time. QHostInfo::fromName(), however, always performs a fresh lookup, overwriting the cache entry.
221     if (! server.useProxy) {
222                 QHostInfo::fromName(server.host);
223         }
224 #ifdef HAVE_SSL
225     if (server.useSsl) {
226         CoreIdentity *identity = identityPtr();
227         if (identity) {
228             socket.setLocalCertificate(identity->sslCert());
229             socket.setPrivateKey(identity->sslKey());
230         }
231         socket.connectToHostEncrypted(server.host, server.port);
232     }
233     else {
234         socket.connectToHost(server.host, server.port);
235     }
236 #else
237     socket.connectToHost(server.host, server.port);
238 #endif
239 }
240
241
242 void CoreNetwork::disconnectFromIrc(bool requested, const QString &reason, bool withReconnect,
243                                     bool forceImmediate)
244 {
245     // Disconnecting from the network, should expect a socket close or error
246     _disconnectExpected = true;
247     _quitRequested = requested; // see socketDisconnected();
248     if (!withReconnect) {
249         _autoReconnectTimer.stop();
250         _autoReconnectCount = 0; // prohibiting auto reconnect
251     }
252     disablePingTimeout();
253     _msgQueue.clear();
254
255     IrcUser *me_ = me();
256     if (me_) {
257         QString awayMsg;
258         if (me_->isAway())
259             awayMsg = me_->awayMessage();
260         Core::setAwayMessage(userId(), networkId(), awayMsg);
261     }
262
263     if (reason.isEmpty() && identityPtr())
264         _quitReason = identityPtr()->quitReason();
265     else
266         _quitReason = reason;
267
268     displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Disconnecting. (%1)").arg((!requested && !withReconnect) ? tr("Core Shutdown") : _quitReason));
269     if (socket.state() == QAbstractSocket::UnconnectedState) {
270         socketDisconnected();
271     } else {
272         if (socket.state() == QAbstractSocket::ConnectedState) {
273             userInputHandler()->issueQuit(_quitReason, forceImmediate);
274         } else {
275             socket.close();
276         }
277         if (requested || withReconnect) {
278             // the irc server has 10 seconds to close the socket
279             _socketCloseTimer.start(10000);
280         }
281     }
282 }
283
284
285 void CoreNetwork::userInput(BufferInfo buf, QString msg)
286 {
287     userInputHandler()->handleUserInput(buf, msg);
288 }
289
290
291 void CoreNetwork::putRawLine(const QByteArray s, const bool prepend)
292 {
293     if (_tokenBucket > 0) {
294         writeToSocket(s);
295     } else {
296         if (prepend) {
297             _msgQueue.prepend(s);
298         } else {
299             _msgQueue.append(s);
300         }
301     }
302 }
303
304
305 void CoreNetwork::putCmd(const QString &cmd, const QList<QByteArray> &params, const QByteArray &prefix, const bool prepend)
306 {
307     QByteArray msg;
308
309     if (!prefix.isEmpty())
310         msg += ":" + prefix + " ";
311     msg += cmd.toUpper().toLatin1();
312
313     for (int i = 0; i < params.size(); i++) {
314         msg += " ";
315
316         if (i == params.size() - 1 && (params[i].contains(' ') || (!params[i].isEmpty() && params[i][0] == ':')))
317             msg += ":";
318
319         msg += params[i];
320     }
321
322     putRawLine(msg, prepend);
323 }
324
325
326 void CoreNetwork::putCmd(const QString &cmd, const QList<QList<QByteArray>> &params, const QByteArray &prefix, const bool prependAll)
327 {
328     QListIterator<QList<QByteArray>> i(params);
329     while (i.hasNext()) {
330         QList<QByteArray> msg = i.next();
331         putCmd(cmd, msg, prefix, prependAll);
332     }
333 }
334
335
336 void CoreNetwork::setChannelJoined(const QString &channel)
337 {
338     _autoWhoQueue.prepend(channel.toLower()); // prepend so this new chan is the first to be checked
339
340     Core::setChannelPersistent(userId(), networkId(), channel, true);
341     Core::setPersistentChannelKey(userId(), networkId(), channel, _channelKeys[channel.toLower()]);
342 }
343
344
345 void CoreNetwork::setChannelParted(const QString &channel)
346 {
347     removeChannelKey(channel);
348     _autoWhoQueue.removeAll(channel.toLower());
349     _autoWhoPending.remove(channel.toLower());
350
351     Core::setChannelPersistent(userId(), networkId(), channel, false);
352 }
353
354
355 void CoreNetwork::addChannelKey(const QString &channel, const QString &key)
356 {
357     if (key.isEmpty()) {
358         removeChannelKey(channel);
359     }
360     else {
361         _channelKeys[channel.toLower()] = key;
362     }
363 }
364
365
366 void CoreNetwork::removeChannelKey(const QString &channel)
367 {
368     _channelKeys.remove(channel.toLower());
369 }
370
371
372 #ifdef HAVE_QCA2
373 Cipher *CoreNetwork::cipher(const QString &target)
374 {
375     if (target.isEmpty())
376         return 0;
377
378     if (!Cipher::neededFeaturesAvailable())
379         return 0;
380
381     CoreIrcChannel *channel = qobject_cast<CoreIrcChannel *>(ircChannel(target));
382     if (channel) {
383         return channel->cipher();
384     }
385     CoreIrcUser *user = qobject_cast<CoreIrcUser *>(ircUser(target));
386     if (user) {
387         return user->cipher();
388     } else if (!isChannelName(target)) {
389         return qobject_cast<CoreIrcUser*>(newIrcUser(target))->cipher();
390     }
391     return 0;
392 }
393
394
395 QByteArray CoreNetwork::cipherKey(const QString &target) const
396 {
397     CoreIrcChannel *c = qobject_cast<CoreIrcChannel*>(ircChannel(target));
398     if (c)
399         return c->cipher()->key();
400
401     CoreIrcUser *u = qobject_cast<CoreIrcUser*>(ircUser(target));
402     if (u)
403         return u->cipher()->key();
404
405     return QByteArray();
406 }
407
408
409 void CoreNetwork::setCipherKey(const QString &target, const QByteArray &key)
410 {
411     CoreIrcChannel *c = qobject_cast<CoreIrcChannel*>(ircChannel(target));
412     if (c) {
413         c->setEncrypted(c->cipher()->setKey(key));
414         return;
415     }
416
417     CoreIrcUser *u = qobject_cast<CoreIrcUser*>(ircUser(target));
418     if (!u && !isChannelName(target))
419         u = qobject_cast<CoreIrcUser*>(newIrcUser(target));
420
421     if (u) {
422         u->setEncrypted(u->cipher()->setKey(key));
423         return;
424     }
425 }
426
427
428 bool CoreNetwork::cipherUsesCBC(const QString &target)
429 {
430     CoreIrcChannel *c = qobject_cast<CoreIrcChannel*>(ircChannel(target));
431     if (c)
432         return c->cipher()->usesCBC();
433     CoreIrcUser *u = qobject_cast<CoreIrcUser*>(ircUser(target));
434     if (u)
435         return u->cipher()->usesCBC();
436
437     return false;
438 }
439 #endif /* HAVE_QCA2 */
440
441 bool CoreNetwork::setAutoWhoDone(const QString &channel)
442 {
443     QString chan = channel.toLower();
444     if (_autoWhoPending.value(chan, 0) <= 0)
445         return false;
446     if (--_autoWhoPending[chan] <= 0)
447         _autoWhoPending.remove(chan);
448     return true;
449 }
450
451
452 void CoreNetwork::setMyNick(const QString &mynick)
453 {
454     Network::setMyNick(mynick);
455     if (connectionState() == Network::Initializing)
456         networkInitialized();
457 }
458
459
460 void CoreNetwork::socketHasData()
461 {
462     while (socket.canReadLine()) {
463         QByteArray s = socket.readLine();
464         if (s.endsWith("\r\n"))
465             s.chop(2);
466         else if (s.endsWith("\n"))
467             s.chop(1);
468         NetworkDataEvent *event = new NetworkDataEvent(EventManager::NetworkIncoming, this, s);
469         event->setTimestamp(QDateTime::currentDateTimeUtc());
470         emit newEvent(event);
471     }
472 }
473
474
475 void CoreNetwork::socketError(QAbstractSocket::SocketError error)
476 {
477     // Ignore socket closed errors if expected
478     if (_disconnectExpected && error == QAbstractSocket::RemoteHostClosedError) {
479         return;
480     }
481
482     _previousConnectionAttemptFailed = true;
483     qWarning() << qPrintable(tr("Could not connect to %1 (%2)").arg(networkName(), socket.errorString()));
484     emit connectionError(socket.errorString());
485     displayMsg(Message::Error, BufferInfo::StatusBuffer, "", tr("Connection failure: %1").arg(socket.errorString()));
486     emitConnectionError(socket.errorString());
487     if (socket.state() < QAbstractSocket::ConnectedState) {
488         socketDisconnected();
489     }
490 }
491
492
493 void CoreNetwork::socketInitialized()
494 {
495     CoreIdentity *identity = identityPtr();
496     if (!identity) {
497         qCritical() << "Identity invalid!";
498         disconnectFromIrc();
499         return;
500     }
501
502     Server server = usedServer();
503
504 #ifdef HAVE_SSL
505     // Non-SSL connections enter here only once, always emit socketInitialized(...) in these cases
506     // SSL connections call socketInitialized() twice, only emit socketInitialized(...) on the first (not yet encrypted) run
507     if (!server.useSsl || !socket.isEncrypted()) {
508         emit socketInitialized(identity, localAddress(), localPort(), peerAddress(), peerPort());
509     }
510
511     if (server.useSsl && !socket.isEncrypted()) {
512         // We'll finish setup once we're encrypted, and called again
513         return;
514     }
515 #else
516     emit socketInitialized(identity, localAddress(), localPort(), peerAddress(), peerPort());
517 #endif
518
519     socket.setSocketOption(QAbstractSocket::KeepAliveOption, true);
520
521     // TokenBucket to avoid sending too much at once
522     _messageDelay = 2200;  // this seems to be a safe value (2.2 seconds delay)
523     _burstSize = 5;
524     _tokenBucket = _burstSize; // init with a full bucket
525     _tokenBucketTimer.start(_messageDelay);
526
527     if (networkInfo().useSasl) {
528         putRawLine(serverEncode(QString("CAP REQ :sasl")));
529     }
530     if (!server.password.isEmpty()) {
531         putRawLine(serverEncode(QString("PASS %1").arg(server.password)));
532     }
533     QString nick;
534     if (identity->nicks().isEmpty()) {
535         nick = "quassel";
536         qWarning() << "CoreNetwork::socketInitialized(): no nicks supplied for identity Id" << identity->id();
537     }
538     else {
539         nick = identity->nicks()[0];
540     }
541     putRawLine(serverEncode(QString("NICK %1").arg(nick)));
542     putRawLine(serverEncode(QString("USER %1 8 * :%2").arg(identity->ident(), identity->realName())));
543 }
544
545
546 void CoreNetwork::socketDisconnected()
547 {
548     disablePingTimeout();
549     _msgQueue.clear();
550
551     _autoWhoCycleTimer.stop();
552     _autoWhoTimer.stop();
553     _autoWhoQueue.clear();
554     _autoWhoPending.clear();
555
556     _socketCloseTimer.stop();
557
558     _tokenBucketTimer.stop();
559
560     IrcUser *me_ = me();
561     if (me_) {
562         foreach(QString channel, me_->channels())
563         displayMsg(Message::Quit, BufferInfo::ChannelBuffer, channel, _quitReason, me_->hostmask());
564     }
565
566     setConnected(false);
567     emit disconnected(networkId());
568     emit socketDisconnected(identityPtr(), localAddress(), localPort(), peerAddress(), peerPort());
569     // Reset disconnect expectations
570     _disconnectExpected = false;
571     if (_quitRequested) {
572         _quitRequested = false;
573         setConnectionState(Network::Disconnected);
574         Core::setNetworkConnected(userId(), networkId(), false);
575     }
576     else if (_autoReconnectCount != 0) {
577         setConnectionState(Network::Reconnecting);
578         if (_autoReconnectCount == -1 || _autoReconnectCount == autoReconnectRetries())
579             doAutoReconnect();  // first try is immediate
580         else
581             _autoReconnectTimer.start();
582     }
583 }
584
585
586 void CoreNetwork::socketStateChanged(QAbstractSocket::SocketState socketState)
587 {
588     Network::ConnectionState state;
589     switch (socketState) {
590     case QAbstractSocket::UnconnectedState:
591         state = Network::Disconnected;
592         socketDisconnected();
593         break;
594     case QAbstractSocket::HostLookupState:
595     case QAbstractSocket::ConnectingState:
596         state = Network::Connecting;
597         break;
598     case QAbstractSocket::ConnectedState:
599         state = Network::Initializing;
600         break;
601     case QAbstractSocket::ClosingState:
602         state = Network::Disconnecting;
603         break;
604     default:
605         state = Network::Disconnected;
606     }
607     setConnectionState(state);
608 }
609
610
611 void CoreNetwork::networkInitialized()
612 {
613     setConnectionState(Network::Initialized);
614     setConnected(true);
615     _disconnectExpected = false;
616     _quitRequested = false;
617
618     if (useAutoReconnect()) {
619         // reset counter
620         _autoReconnectCount = unlimitedReconnectRetries() ? -1 : autoReconnectRetries();
621     }
622
623     // restore away state
624     QString awayMsg = Core::awayMessage(userId(), networkId());
625     if (!awayMsg.isEmpty())
626         userInputHandler()->handleAway(BufferInfo(), Core::awayMessage(userId(), networkId()));
627
628     sendPerform();
629
630     _sendPings = true;
631
632     if (networkConfig()->autoWhoEnabled()) {
633         _autoWhoCycleTimer.start();
634         _autoWhoTimer.start();
635         startAutoWhoCycle(); // FIXME wait for autojoin to be completed
636     }
637
638     Core::bufferInfo(userId(), networkId(), BufferInfo::StatusBuffer); // create status buffer
639     Core::setNetworkConnected(userId(), networkId(), true);
640 }
641
642
643 void CoreNetwork::sendPerform()
644 {
645     BufferInfo statusBuf = BufferInfo::fakeStatusBuffer(networkId());
646
647     // do auto identify
648     if (useAutoIdentify() && !autoIdentifyService().isEmpty() && !autoIdentifyPassword().isEmpty()) {
649         userInputHandler()->handleMsg(statusBuf, QString("%1 IDENTIFY %2").arg(autoIdentifyService(), autoIdentifyPassword()));
650     }
651
652     // restore old user modes if server default mode is set.
653     IrcUser *me_ = me();
654     if (me_) {
655         if (!me_->userModes().isEmpty()) {
656             restoreUserModes();
657         }
658         else {
659             connect(me_, SIGNAL(userModesSet(QString)), this, SLOT(restoreUserModes()));
660             connect(me_, SIGNAL(userModesAdded(QString)), this, SLOT(restoreUserModes()));
661         }
662     }
663
664     // send perform list
665     foreach(QString line, perform()) {
666         if (!line.isEmpty()) userInput(statusBuf, line);
667     }
668
669     // rejoin channels we've been in
670     if (rejoinChannels()) {
671         QStringList channels, keys;
672         foreach(QString chan, coreSession()->persistentChannels(networkId()).keys()) {
673             QString key = channelKey(chan);
674             if (!key.isEmpty()) {
675                 channels.prepend(chan);
676                 keys.prepend(key);
677             }
678             else {
679                 channels.append(chan);
680             }
681         }
682         QString joinString = QString("%1 %2").arg(channels.join(",")).arg(keys.join(",")).trimmed();
683         if (!joinString.isEmpty())
684             userInputHandler()->handleJoin(statusBuf, joinString);
685     }
686 }
687
688
689 void CoreNetwork::restoreUserModes()
690 {
691     IrcUser *me_ = me();
692     Q_ASSERT(me_);
693
694     disconnect(me_, SIGNAL(userModesSet(QString)), this, SLOT(restoreUserModes()));
695     disconnect(me_, SIGNAL(userModesAdded(QString)), this, SLOT(restoreUserModes()));
696
697     QString modesDelta = Core::userModes(userId(), networkId());
698     QString currentModes = me_->userModes();
699
700     QString addModes, removeModes;
701     if (modesDelta.contains('-')) {
702         addModes = modesDelta.section('-', 0, 0);
703         removeModes = modesDelta.section('-', 1);
704     }
705     else {
706         addModes = modesDelta;
707     }
708
709     addModes.remove(QRegExp(QString("[%1]").arg(currentModes)));
710     if (currentModes.isEmpty())
711         removeModes = QString();
712     else
713         removeModes.remove(QRegExp(QString("[^%1]").arg(currentModes)));
714
715     if (addModes.isEmpty() && removeModes.isEmpty())
716         return;
717
718     if (!addModes.isEmpty())
719         addModes = '+' + addModes;
720     if (!removeModes.isEmpty())
721         removeModes = '-' + removeModes;
722
723     // don't use InputHandler::handleMode() as it keeps track of our persistent mode changes
724     putRawLine(serverEncode(QString("MODE %1 %2%3").arg(me_->nick()).arg(addModes).arg(removeModes)));
725 }
726
727
728 void CoreNetwork::updateIssuedModes(const QString &requestedModes)
729 {
730     QString addModes;
731     QString removeModes;
732     bool addMode = true;
733
734     for (int i = 0; i < requestedModes.length(); i++) {
735         if (requestedModes[i] == '+') {
736             addMode = true;
737             continue;
738         }
739         if (requestedModes[i] == '-') {
740             addMode = false;
741             continue;
742         }
743         if (addMode) {
744             addModes += requestedModes[i];
745         }
746         else {
747             removeModes += requestedModes[i];
748         }
749     }
750
751     QString addModesOld = _requestedUserModes.section('-', 0, 0);
752     QString removeModesOld = _requestedUserModes.section('-', 1);
753
754     addModes.remove(QRegExp(QString("[%1]").arg(addModesOld))); // deduplicate
755     addModesOld.remove(QRegExp(QString("[%1]").arg(removeModes))); // update
756     addModes += addModesOld;
757
758     removeModes.remove(QRegExp(QString("[%1]").arg(removeModesOld))); // deduplicate
759     removeModesOld.remove(QRegExp(QString("[%1]").arg(addModes))); // update
760     removeModes += removeModesOld;
761
762     _requestedUserModes = QString("%1-%2").arg(addModes).arg(removeModes);
763 }
764
765
766 void CoreNetwork::updatePersistentModes(QString addModes, QString removeModes)
767 {
768     QString persistentUserModes = Core::userModes(userId(), networkId());
769
770     QString requestedAdd = _requestedUserModes.section('-', 0, 0);
771     QString requestedRemove = _requestedUserModes.section('-', 1);
772
773     QString persistentAdd, persistentRemove;
774     if (persistentUserModes.contains('-')) {
775         persistentAdd = persistentUserModes.section('-', 0, 0);
776         persistentRemove = persistentUserModes.section('-', 1);
777     }
778     else {
779         persistentAdd = persistentUserModes;
780     }
781
782     // remove modes we didn't issue
783     if (requestedAdd.isEmpty())
784         addModes = QString();
785     else
786         addModes.remove(QRegExp(QString("[^%1]").arg(requestedAdd)));
787
788     if (requestedRemove.isEmpty())
789         removeModes = QString();
790     else
791         removeModes.remove(QRegExp(QString("[^%1]").arg(requestedRemove)));
792
793     // deduplicate
794     persistentAdd.remove(QRegExp(QString("[%1]").arg(addModes)));
795     persistentRemove.remove(QRegExp(QString("[%1]").arg(removeModes)));
796
797     // update
798     persistentAdd.remove(QRegExp(QString("[%1]").arg(removeModes)));
799     persistentRemove.remove(QRegExp(QString("[%1]").arg(addModes)));
800
801     // update issued mode list
802     requestedAdd.remove(QRegExp(QString("[%1]").arg(addModes)));
803     requestedRemove.remove(QRegExp(QString("[%1]").arg(removeModes)));
804     _requestedUserModes = QString("%1-%2").arg(requestedAdd).arg(requestedRemove);
805
806     persistentAdd += addModes;
807     persistentRemove += removeModes;
808     Core::setUserModes(userId(), networkId(), QString("%1-%2").arg(persistentAdd).arg(persistentRemove));
809 }
810
811
812 void CoreNetwork::resetPersistentModes()
813 {
814     _requestedUserModes = QString('-');
815     Core::setUserModes(userId(), networkId(), QString());
816 }
817
818
819 void CoreNetwork::setUseAutoReconnect(bool use)
820 {
821     Network::setUseAutoReconnect(use);
822     if (!use)
823         _autoReconnectTimer.stop();
824 }
825
826
827 void CoreNetwork::setAutoReconnectInterval(quint32 interval)
828 {
829     Network::setAutoReconnectInterval(interval);
830     _autoReconnectTimer.setInterval(interval * 1000);
831 }
832
833
834 void CoreNetwork::setAutoReconnectRetries(quint16 retries)
835 {
836     Network::setAutoReconnectRetries(retries);
837     if (_autoReconnectCount != 0) {
838         if (unlimitedReconnectRetries())
839             _autoReconnectCount = -1;
840         else
841             _autoReconnectCount = autoReconnectRetries();
842     }
843 }
844
845
846 void CoreNetwork::doAutoReconnect()
847 {
848     if (connectionState() != Network::Disconnected && connectionState() != Network::Reconnecting) {
849         qWarning() << "CoreNetwork::doAutoReconnect(): Cannot reconnect while not being disconnected!";
850         return;
851     }
852     if (_autoReconnectCount > 0 || _autoReconnectCount == -1)
853         _autoReconnectCount--;  // -2 means we delay the next reconnect
854     connectToIrc(true);
855 }
856
857
858 void CoreNetwork::sendPing()
859 {
860     uint now = QDateTime::currentDateTime().toTime_t();
861     if (_pingCount != 0) {
862         qDebug() << "UserId:" << userId() << "Network:" << networkName() << "missed" << _pingCount << "pings."
863                  << "BA:" << socket.bytesAvailable() << "BTW:" << socket.bytesToWrite();
864     }
865     if ((int)_pingCount >= networkConfig()->maxPingCount() && now - _lastPingTime <= (uint)(_pingTimer.interval() / 1000) + 1) {
866         // the second check compares the actual elapsed time since the last ping and the pingTimer interval
867         // if the interval is shorter then the actual elapsed time it means that this thread was somehow blocked
868         // and unable to even handle a ping answer. So we ignore those misses.
869         disconnectFromIrc(false, QString("No Ping reply in %1 seconds.").arg(_pingCount * _pingTimer.interval() / 1000), true /* withReconnect */);
870     }
871     else {
872         _lastPingTime = now;
873         _pingCount++;
874         // Don't send pings until the network is initialized
875         if(_sendPings)
876             userInputHandler()->handlePing(BufferInfo(), QString());
877     }
878 }
879
880
881 void CoreNetwork::enablePingTimeout(bool enable)
882 {
883     if (!enable)
884         disablePingTimeout();
885     else {
886         resetPingTimeout();
887         if (networkConfig()->pingTimeoutEnabled())
888             _pingTimer.start();
889     }
890 }
891
892
893 void CoreNetwork::disablePingTimeout()
894 {
895     _pingTimer.stop();
896     _sendPings = false;
897     resetPingTimeout();
898 }
899
900
901 void CoreNetwork::setPingInterval(int interval)
902 {
903     _pingTimer.setInterval(interval * 1000);
904 }
905
906
907 /******** AutoWHO ********/
908
909 void CoreNetwork::startAutoWhoCycle()
910 {
911     if (!_autoWhoQueue.isEmpty()) {
912         _autoWhoCycleTimer.stop();
913         return;
914     }
915     _autoWhoQueue = channels();
916 }
917
918
919 void CoreNetwork::setAutoWhoDelay(int delay)
920 {
921     _autoWhoTimer.setInterval(delay * 1000);
922 }
923
924
925 void CoreNetwork::setAutoWhoInterval(int interval)
926 {
927     _autoWhoCycleTimer.setInterval(interval * 1000);
928 }
929
930
931 void CoreNetwork::setAutoWhoEnabled(bool enabled)
932 {
933     if (enabled && isConnected() && !_autoWhoTimer.isActive())
934         _autoWhoTimer.start();
935     else if (!enabled) {
936         _autoWhoTimer.stop();
937         _autoWhoCycleTimer.stop();
938     }
939 }
940
941
942 void CoreNetwork::sendAutoWho()
943 {
944     // Don't send autowho if there are still some pending
945     if (_autoWhoPending.count())
946         return;
947
948     while (!_autoWhoQueue.isEmpty()) {
949         QString chan = _autoWhoQueue.takeFirst();
950         IrcChannel *ircchan = ircChannel(chan);
951         if (!ircchan) continue;
952         if (networkConfig()->autoWhoNickLimit() > 0 && ircchan->ircUsers().count() >= networkConfig()->autoWhoNickLimit())
953             continue;
954         _autoWhoPending[chan]++;
955         putRawLine("WHO " + serverEncode(chan));
956         break;
957     }
958     if (_autoWhoQueue.isEmpty() && networkConfig()->autoWhoEnabled() && !_autoWhoCycleTimer.isActive()) {
959         // Timer was stopped, means a new cycle is due immediately
960         _autoWhoCycleTimer.start();
961         startAutoWhoCycle();
962     }
963 }
964
965
966 #ifdef HAVE_SSL
967 void CoreNetwork::sslErrors(const QList<QSslError> &sslErrors)
968 {
969     Q_UNUSED(sslErrors)
970     socket.ignoreSslErrors();
971     // TODO errorhandling
972 }
973
974
975 #endif  // HAVE_SSL
976
977 void CoreNetwork::fillBucketAndProcessQueue()
978 {
979     if (_tokenBucket < _burstSize) {
980         _tokenBucket++;
981     }
982
983     while (_msgQueue.size() > 0 && _tokenBucket > 0) {
984         writeToSocket(_msgQueue.takeFirst());
985     }
986 }
987
988
989 void CoreNetwork::writeToSocket(const QByteArray &data)
990 {
991     socket.write(data);
992     socket.write("\r\n");
993     _tokenBucket--;
994 }
995
996
997 Network::Server CoreNetwork::usedServer() const
998 {
999     if (_lastUsedServerIndex < serverList().count())
1000         return serverList()[_lastUsedServerIndex];
1001
1002     if (!serverList().isEmpty())
1003         return serverList()[0];
1004
1005     return Network::Server();
1006 }
1007
1008
1009 void CoreNetwork::requestConnect() const
1010 {
1011     if (connectionState() != Disconnected) {
1012         qWarning() << "Requesting connect while already being connected!";
1013         return;
1014     }
1015     QMetaObject::invokeMethod(const_cast<CoreNetwork *>(this), "connectToIrc", Qt::QueuedConnection);
1016 }
1017
1018
1019 void CoreNetwork::requestDisconnect() const
1020 {
1021     if (connectionState() == Disconnected) {
1022         qWarning() << "Requesting disconnect while not being connected!";
1023         return;
1024     }
1025     userInputHandler()->handleQuit(BufferInfo(), QString());
1026 }
1027
1028
1029 void CoreNetwork::requestSetNetworkInfo(const NetworkInfo &info)
1030 {
1031     Network::Server currentServer = usedServer();
1032     setNetworkInfo(info);
1033     Core::updateNetwork(coreSession()->user(), info);
1034
1035     // the order of the servers might have changed,
1036     // so we try to find the previously used server
1037     _lastUsedServerIndex = 0;
1038     for (int i = 0; i < serverList().count(); i++) {
1039         Network::Server server = serverList()[i];
1040         if (server.host == currentServer.host && server.port == currentServer.port) {
1041             _lastUsedServerIndex = i;
1042             break;
1043         }
1044     }
1045 }
1046
1047
1048 QList<QList<QByteArray>> CoreNetwork::splitMessage(const QString &cmd, const QString &message, std::function<QList<QByteArray>(QString &)> cmdGenerator)
1049 {
1050     QString wrkMsg(message);
1051     QList<QList<QByteArray>> msgsToSend;
1052
1053     // do while (wrkMsg.size() > 0)
1054     do {
1055         // First, check to see if the whole message can be sent at once.  The
1056         // cmdGenerator function is passed in by the caller and is used to encode
1057         // and encrypt (if applicable) the message, since different callers might
1058         // want to use different encoding or encode different values.
1059         int splitPos = wrkMsg.size();
1060         QList<QByteArray> initialSplitMsgEnc = cmdGenerator(wrkMsg);
1061         int initialOverrun = userInputHandler()->lastParamOverrun(cmd, initialSplitMsgEnc);
1062
1063         if (initialOverrun) {
1064             // If the message was too long to be sent, first try splitting it along
1065             // word boundaries with QTextBoundaryFinder.
1066             QString splitMsg(wrkMsg);
1067             QTextBoundaryFinder qtbf(QTextBoundaryFinder::Word, splitMsg);
1068             qtbf.setPosition(initialSplitMsgEnc[1].size() - initialOverrun);
1069             QList<QByteArray> splitMsgEnc;
1070             int overrun = initialOverrun;
1071
1072             while (overrun) {
1073                 splitPos = qtbf.toPreviousBoundary();
1074
1075                 // splitPos==-1 means the QTBF couldn't find a split point at all and
1076                 // splitPos==0 means the QTBF could only find a boundary at the beginning of
1077                 // the string.  Neither one of these works for us.
1078                 if (splitPos > 0) {
1079                     // If a split point could be found, split the message there, calculate the
1080                     // overrun, and continue with the loop.
1081                     splitMsg = splitMsg.left(splitPos);
1082                     splitMsgEnc = cmdGenerator(splitMsg);
1083                     overrun = userInputHandler()->lastParamOverrun(cmd, splitMsgEnc);
1084                 }
1085                 else {
1086                     // If a split point could not be found (the beginning of the message
1087                     // is reached without finding a split point short enough to send) and we
1088                     // are still in Word mode, switch to Grapheme mode.  We also need to restore
1089                     // the full wrkMsg to splitMsg, since splitMsg may have been cut down during
1090                     // the previous attempt to find a split point.
1091                     if (qtbf.type() == QTextBoundaryFinder::Word) {
1092                         splitMsg = wrkMsg;
1093                         splitPos = splitMsg.size();
1094                         QTextBoundaryFinder graphemeQtbf(QTextBoundaryFinder::Grapheme, splitMsg);
1095                         graphemeQtbf.setPosition(initialSplitMsgEnc[1].size() - initialOverrun);
1096                         qtbf = graphemeQtbf;
1097                     }
1098                     else {
1099                         // If the QTBF fails to find a split point in Grapheme mode, we give up.
1100                         // This should never happen, but it should be handled anyway.
1101                         qWarning() << "Unexpected failure to split message!";
1102                         return msgsToSend;
1103                     }
1104                 }
1105             }
1106
1107             // Once a message of sendable length has been found, remove it from the wrkMsg and
1108             // add it to the list of messages to be sent.
1109             wrkMsg.remove(0, splitPos);
1110             msgsToSend.append(splitMsgEnc);
1111         }
1112         else{
1113             // If the entire remaining message is short enough to be sent all at once, remove
1114             // it from the wrkMsg and add it to the list of messages to be sent.
1115             wrkMsg.remove(0, splitPos);
1116             msgsToSend.append(initialSplitMsgEnc);
1117         }
1118     } while (wrkMsg.size() > 0);
1119
1120     return msgsToSend;
1121 }