cmake: Don't needlessly sync translations
[quassel.git] / src / core / corenetwork.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2019 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 "corenetwork.h"
22
23 #include <QDebug>
24 #include <QHostInfo>
25
26 #include "core.h"
27 #include "coreidentity.h"
28 #include "corenetworkconfig.h"
29 #include "coresession.h"
30 #include "coreuserinputhandler.h"
31 #include "ircencoder.h"
32 #include "irccap.h"
33 #include "irctag.h"
34 #include "networkevent.h"
35
36 CoreNetwork::CoreNetwork(const NetworkId& networkid, CoreSession* session)
37     : Network(networkid, session)
38     , _coreSession(session)
39     , _userInputHandler(new CoreUserInputHandler(this))
40     , _metricsServer(Core::instance()->metricsServer())
41     , _autoReconnectCount(0)
42     , _quitRequested(false)
43     , _disconnectExpected(false)
44     , _previousConnectionAttemptFailed(false)
45     , _lastUsedServerIndex(0)
46     , _requestedUserModes('-')
47 {
48     // Check if raw IRC logging is enabled
49     _debugLogRawIrc = (Quassel::isOptionSet("debug-irc") || Quassel::isOptionSet("debug-irc-id"));
50     _debugLogRawNetId = Quassel::optionValue("debug-irc-id").toInt();
51
52     _autoReconnectTimer.setSingleShot(true);
53     connect(&_socketCloseTimer, &QTimer::timeout, this, &CoreNetwork::onSocketCloseTimeout);
54
55     setPingInterval(networkConfig()->pingInterval());
56     connect(&_pingTimer, &QTimer::timeout, this, &CoreNetwork::sendPing);
57
58     setAutoWhoDelay(networkConfig()->autoWhoDelay());
59     setAutoWhoInterval(networkConfig()->autoWhoInterval());
60
61     QHash<QString, QString> channels = coreSession()->persistentChannels(networkId());
62     for (const QString& chan : channels.keys()) {
63         _channelKeys[chan.toLower()] = channels[chan];
64     }
65
66     QHash<QString, QByteArray> bufferCiphers = coreSession()->bufferCiphers(networkId());
67     for (const QString& buffer : bufferCiphers.keys()) {
68         storeChannelCipherKey(buffer.toLower(), bufferCiphers[buffer]);
69     }
70
71     connect(networkConfig(), &NetworkConfig::pingTimeoutEnabledSet, this, &CoreNetwork::enablePingTimeout);
72     connect(networkConfig(), &NetworkConfig::pingIntervalSet, this, &CoreNetwork::setPingInterval);
73     connect(networkConfig(), &NetworkConfig::autoWhoEnabledSet, this, &CoreNetwork::setAutoWhoEnabled);
74     connect(networkConfig(), &NetworkConfig::autoWhoIntervalSet, this, &CoreNetwork::setAutoWhoInterval);
75     connect(networkConfig(), &NetworkConfig::autoWhoDelaySet, this, &CoreNetwork::setAutoWhoDelay);
76
77     connect(&_autoReconnectTimer, &QTimer::timeout, this, &CoreNetwork::doAutoReconnect);
78     connect(&_autoWhoTimer, &QTimer::timeout, this, &CoreNetwork::sendAutoWho);
79     connect(&_autoWhoCycleTimer, &QTimer::timeout, this, &CoreNetwork::startAutoWhoCycle);
80     connect(&_tokenBucketTimer, &QTimer::timeout, this, &CoreNetwork::checkTokenBucket);
81
82     connect(&socket, &QAbstractSocket::connected, this, &CoreNetwork::onSocketInitialized);
83     connect(&socket, selectOverload<QAbstractSocket::SocketError>(&QAbstractSocket::error), this, &CoreNetwork::onSocketError);
84     connect(&socket, &QAbstractSocket::stateChanged, this, &CoreNetwork::onSocketStateChanged);
85     connect(&socket, &QIODevice::readyRead, this, &CoreNetwork::onSocketHasData);
86 #ifdef HAVE_SSL
87     connect(&socket, &QSslSocket::encrypted, this, &CoreNetwork::onSocketInitialized);
88     connect(&socket, selectOverload<const QList<QSslError>&>(&QSslSocket::sslErrors), this, &CoreNetwork::onSslErrors);
89 #endif
90     connect(this, &CoreNetwork::newEvent, coreSession()->eventManager(), &EventManager::postEvent);
91
92     // Custom rate limiting
93     // These react to the user changing settings in the client
94     connect(this, &Network::useCustomMessageRateSet, this, &CoreNetwork::updateRateLimiting);
95     connect(this, &Network::messageRateBurstSizeSet, this, &CoreNetwork::updateRateLimiting);
96     connect(this, &Network::messageRateDelaySet, this, &CoreNetwork::updateRateLimiting);
97     connect(this, &Network::unlimitedMessageRateSet, this, &CoreNetwork::updateRateLimiting);
98
99     // IRCv3 capability handling
100     // These react to CAP messages from the server
101     connect(this, &Network::capAdded, this, &CoreNetwork::serverCapAdded);
102     connect(this, &Network::capAcknowledged, this, &CoreNetwork::serverCapAcknowledged);
103     connect(this, &Network::capRemoved, this, &CoreNetwork::serverCapRemoved);
104
105     if (Quassel::isOptionSet("oidentd")) {
106         connect(this, &CoreNetwork::socketInitialized, Core::instance()->oidentdConfigGenerator(), &OidentdConfigGenerator::addSocket);
107         connect(this, &CoreNetwork::socketDisconnected, Core::instance()->oidentdConfigGenerator(), &OidentdConfigGenerator::removeSocket);
108     }
109
110     if (Quassel::isOptionSet("ident-daemon")) {
111         connect(this, &CoreNetwork::socketInitialized, Core::instance()->identServer(), &IdentServer::addSocket);
112         connect(this, &CoreNetwork::socketDisconnected, Core::instance()->identServer(), &IdentServer::removeSocket);
113     }
114 }
115
116 CoreNetwork::~CoreNetwork()
117 {
118     // Ensure we don't get any more signals from the socket while shutting down
119     disconnect(&socket, nullptr, this, nullptr);
120     if (!forceDisconnect()) {
121         qWarning() << QString{"Could not disconnect from network %1 (network ID: %2, user ID: %3)"}
122             .arg(networkName())
123             .arg(networkId().toInt())
124             .arg(userId().toInt());
125     }
126 }
127
128 bool CoreNetwork::forceDisconnect(int msecs)
129 {
130     if (socket.state() == QAbstractSocket::UnconnectedState) {
131         // Socket already disconnected.
132         return true;
133     }
134     // Request a socket-level disconnect if not already happened
135     socket.disconnectFromHost();
136     if (socket.state() != QAbstractSocket::UnconnectedState) {
137         return socket.waitForDisconnected(msecs);
138     }
139     return true;
140 }
141
142 QString CoreNetwork::channelDecode(const QString& bufferName, const QByteArray& string) const
143 {
144     if (!bufferName.isEmpty()) {
145         IrcChannel* channel = ircChannel(bufferName);
146         if (channel)
147             return channel->decodeString(string);
148     }
149     return decodeString(string);
150 }
151
152 QString CoreNetwork::userDecode(const QString& userNick, const QByteArray& string) const
153 {
154     IrcUser* user = ircUser(userNick);
155     if (user)
156         return user->decodeString(string);
157     return decodeString(string);
158 }
159
160 QByteArray CoreNetwork::channelEncode(const QString& bufferName, const QString& string) const
161 {
162     if (!bufferName.isEmpty()) {
163         IrcChannel* channel = ircChannel(bufferName);
164         if (channel)
165             return channel->encodeString(string);
166     }
167     return encodeString(string);
168 }
169
170 QByteArray CoreNetwork::userEncode(const QString& userNick, const QString& string) const
171 {
172     IrcUser* user = ircUser(userNick);
173     if (user)
174         return user->encodeString(string);
175     return encodeString(string);
176 }
177
178 void CoreNetwork::connectToIrc(bool reconnecting)
179 {
180     if (_shuttingDown) {
181         return;
182     }
183
184     if (Core::instance()->identServer()) {
185         _socketId = Core::instance()->identServer()->addWaitingSocket();
186     }
187
188     if (_metricsServer) {
189         _metricsServer->addNetwork(userId());
190     }
191
192     if (!reconnecting && useAutoReconnect() && _autoReconnectCount == 0) {
193         _autoReconnectTimer.setInterval(autoReconnectInterval() * 1000);
194         if (unlimitedReconnectRetries())
195             _autoReconnectCount = -1;
196         else
197             _autoReconnectCount = autoReconnectRetries();
198     }
199     if (serverList().isEmpty()) {
200         qWarning() << "Server list empty, ignoring connect request!";
201         return;
202     }
203     CoreIdentity* identity = identityPtr();
204     if (!identity) {
205         qWarning() << "Invalid identity configures, ignoring connect request!";
206         return;
207     }
208
209     // cleaning up old quit reason
210     _quitReason.clear();
211
212     // Reset capability negotiation tracking, also handling server changes during reconnect
213     _capsQueuedIndividual.clear();
214     _capsQueuedBundled.clear();
215     clearCaps();
216     _capNegotiationActive = false;
217     _capInitialNegotiationEnded = false;
218
219     // use a random server?
220     if (useRandomServer()) {
221         _lastUsedServerIndex = qrand() % serverList().size();
222     }
223     else if (_previousConnectionAttemptFailed) {
224         // cycle to next server if previous connection attempt failed
225         _previousConnectionAttemptFailed = false;
226         showMessage(NetworkInternalMessage(
227             Message::Server,
228             BufferInfo::StatusBuffer,
229             "",
230             tr("Connection failed. Cycling to next server...")
231         ));
232         if (++_lastUsedServerIndex >= serverList().size()) {
233             _lastUsedServerIndex = 0;
234         }
235     }
236     else {
237         // Start out with the top server in the list
238         _lastUsedServerIndex = 0;
239     }
240
241     Server server = usedServer();
242     displayStatusMsg(tr("Connecting to %1:%2...").arg(server.host).arg(server.port));
243     showMessage(NetworkInternalMessage(
244         Message::Server,
245         BufferInfo::StatusBuffer,
246         "",
247         tr("Connecting to %1:%2...").arg(server.host).arg(server.port)
248     ));
249
250     if (server.useProxy) {
251         QNetworkProxy proxy((QNetworkProxy::ProxyType) server.proxyType, server.proxyHost, server.proxyPort, server.proxyUser, server.proxyPass);
252         socket.setProxy(proxy);
253     }
254     else {
255         socket.setProxy(QNetworkProxy::NoProxy);
256     }
257
258     enablePingTimeout();
259
260     // Reset tracking for valid timestamps in PONG replies
261     setPongTimestampValid(false);
262
263     // Qt caches DNS entries for a minute, resulting in round-robin (e.g. for chat.freenode.net) not working if several users
264     // connect at a similar time. QHostInfo::fromName(), however, always performs a fresh lookup, overwriting the cache entry.
265     if (!server.useProxy) {
266         // Avoid hostname lookups when a proxy is specified. The lookups won't use the proxy and may therefore leak the DNS
267         // hostname of the server. Qt's DNS cache also isn't used by the proxy so we don't need to refresh the entry.
268         QHostInfo::fromName(server.host);
269     }
270 #ifdef HAVE_SSL
271     if (server.useSsl) {
272         CoreIdentity* identity = identityPtr();
273         if (identity) {
274             socket.setLocalCertificate(identity->sslCert());
275             socket.setPrivateKey(identity->sslKey());
276         }
277         socket.connectToHostEncrypted(server.host, server.port);
278     }
279     else {
280         socket.connectToHost(server.host, server.port);
281     }
282 #else
283     socket.connectToHost(server.host, server.port);
284 #endif
285 }
286
287 void CoreNetwork::disconnectFromIrc(bool requested, const QString& reason, bool withReconnect)
288 {
289     // Disconnecting from the network, should expect a socket close or error
290     _disconnectExpected = true;
291     _quitRequested = requested;  // see socketDisconnected();
292     if (!withReconnect) {
293         _autoReconnectTimer.stop();
294         _autoReconnectCount = 0;  // prohibiting auto reconnect
295     }
296     disablePingTimeout();
297     _msgQueue.clear();
298     if (_metricsServer) {
299         _metricsServer->messageQueue(userId(), 0);
300     }
301
302     IrcUser* me_ = me();
303     if (me_) {
304         QString awayMsg;
305         if (me_->isAway())
306             awayMsg = me_->awayMessage();
307         Core::setAwayMessage(userId(), networkId(), awayMsg);
308     }
309
310     if (reason.isEmpty() && identityPtr())
311         _quitReason = identityPtr()->quitReason();
312     else
313         _quitReason = reason;
314
315     showMessage(NetworkInternalMessage(
316         Message::Server,
317         BufferInfo::StatusBuffer,
318         "",
319         tr("Disconnecting. (%1)").arg((!requested && !withReconnect) ? tr("Core Shutdown") : _quitReason)
320     ));
321     if (socket.state() == QAbstractSocket::UnconnectedState) {
322         onSocketDisconnected();
323     }
324     else {
325         if (socket.state() == QAbstractSocket::ConnectedState) {
326             // If shutting down, prioritize the QUIT command
327             userInputHandler()->issueQuit(_quitReason, _shuttingDown);
328         }
329         else {
330             socket.close();
331         }
332         if (socket.state() != QAbstractSocket::UnconnectedState) {
333             // Wait for up to 10 seconds for the socket to close cleanly, then it will be forcefully aborted
334             _socketCloseTimer.start(10000);
335         }
336     }
337 }
338
339 void CoreNetwork::onSocketCloseTimeout()
340 {
341     qWarning() << QString{"Timed out quitting network %1 (network ID: %2, user ID: %3)"}
342         .arg(networkName())
343         .arg(networkId().toInt())
344         .arg(userId().toInt());
345     socket.abort();
346 }
347
348 void CoreNetwork::shutdown()
349 {
350     _shuttingDown = true;
351     disconnectFromIrc(false, {}, false);
352 }
353
354 void CoreNetwork::userInput(const BufferInfo& buf, QString msg)
355 {
356     userInputHandler()->handleUserInput(buf, msg);
357 }
358
359 void CoreNetwork::putRawLine(const QByteArray& s, bool prepend)
360 {
361     if (_tokenBucket > 0 || (_skipMessageRates && _msgQueue.isEmpty())) {
362         // If there's tokens remaining, ...
363         // Or rate limits don't apply AND no messages are in queue (to prevent out-of-order), ...
364         // Send the message now.
365         writeToSocket(s);
366     }
367     else {
368         // Otherwise, queue the message for later
369         if (prepend) {
370             // Jump to the start, skipping other messages
371             _msgQueue.prepend(s);
372         }
373         else {
374             // Add to back, waiting in order
375             _msgQueue.append(s);
376         }
377         if (_metricsServer) {
378             _metricsServer->messageQueue(userId(), _msgQueue.size());
379         }
380     }
381 }
382
383 void CoreNetwork::putCmd(const QString& cmd, const QList<QByteArray>& params, const QByteArray& prefix, const QHash<IrcTagKey, QString>& tags, bool prepend)
384 {
385     putRawLine(IrcEncoder::writeMessage(tags, prefix, cmd, params), prepend);
386 }
387
388 void CoreNetwork::putCmd(const QString& cmd, const QList<QList<QByteArray>>& params, const QByteArray& prefix, const QHash<IrcTagKey, QString>& tags, bool prependAll)
389 {
390     QListIterator<QList<QByteArray>> i(params);
391     while (i.hasNext()) {
392         QList<QByteArray> msg = i.next();
393         putCmd(cmd, msg, prefix, tags, prependAll);
394     }
395 }
396
397 void CoreNetwork::setChannelJoined(const QString& channel)
398 {
399     queueAutoWhoOneshot(channel);  // check this new channel first
400
401     Core::setChannelPersistent(userId(), networkId(), channel, true);
402     Core::setPersistentChannelKey(userId(), networkId(), channel, _channelKeys[channel.toLower()]);
403 }
404
405 void CoreNetwork::setChannelParted(const QString& channel)
406 {
407     removeChannelKey(channel);
408     _autoWhoQueue.removeAll(channel.toLower());
409     _autoWhoPending.remove(channel.toLower());
410
411     Core::setChannelPersistent(userId(), networkId(), channel, false);
412 }
413
414 void CoreNetwork::addChannelKey(const QString& channel, const QString& key)
415 {
416     if (key.isEmpty()) {
417         removeChannelKey(channel);
418     }
419     else {
420         _channelKeys[channel.toLower()] = key;
421     }
422 }
423
424 void CoreNetwork::removeChannelKey(const QString& channel)
425 {
426     _channelKeys.remove(channel.toLower());
427 }
428
429 #ifdef HAVE_QCA2
430 Cipher* CoreNetwork::cipher(const QString& target)
431 {
432     if (target.isEmpty())
433         return nullptr;
434
435     if (!Cipher::neededFeaturesAvailable())
436         return nullptr;
437
438     auto* channel = qobject_cast<CoreIrcChannel*>(ircChannel(target));
439     if (channel) {
440         return channel->cipher();
441     }
442     auto* user = qobject_cast<CoreIrcUser*>(ircUser(target));
443     if (user) {
444         return user->cipher();
445     }
446     else if (!isChannelName(target)) {
447         return qobject_cast<CoreIrcUser*>(newIrcUser(target))->cipher();
448     }
449     return nullptr;
450 }
451
452 QByteArray CoreNetwork::cipherKey(const QString& target) const
453 {
454     auto* c = qobject_cast<CoreIrcChannel*>(ircChannel(target));
455     if (c)
456         return c->cipher()->key();
457
458     auto* u = qobject_cast<CoreIrcUser*>(ircUser(target));
459     if (u)
460         return u->cipher()->key();
461
462     return QByteArray();
463 }
464
465 void CoreNetwork::setCipherKey(const QString& target, const QByteArray& key)
466 {
467     auto* c = qobject_cast<CoreIrcChannel*>(ircChannel(target));
468     if (c) {
469         c->setEncrypted(c->cipher()->setKey(key));
470         coreSession()->setBufferCipher(networkId(), target, key);
471         return;
472     }
473
474     auto* u = qobject_cast<CoreIrcUser*>(ircUser(target));
475     if (!u && !isChannelName(target))
476         u = qobject_cast<CoreIrcUser*>(newIrcUser(target));
477
478     if (u) {
479         u->setEncrypted(u->cipher()->setKey(key));
480         coreSession()->setBufferCipher(networkId(), target, key);
481         return;
482     }
483 }
484
485 bool CoreNetwork::cipherUsesCBC(const QString& target)
486 {
487     auto* c = qobject_cast<CoreIrcChannel*>(ircChannel(target));
488     if (c)
489         return c->cipher()->usesCBC();
490     auto* u = qobject_cast<CoreIrcUser*>(ircUser(target));
491     if (u)
492         return u->cipher()->usesCBC();
493
494     return false;
495 }
496 #endif /* HAVE_QCA2 */
497
498 bool CoreNetwork::setAutoWhoDone(const QString& name)
499 {
500     QString chanOrNick = name.toLower();
501     if (_autoWhoPending.value(chanOrNick, 0) <= 0)
502         return false;
503     if (--_autoWhoPending[chanOrNick] <= 0)
504         _autoWhoPending.remove(chanOrNick);
505     return true;
506 }
507
508 void CoreNetwork::setMyNick(const QString& mynick)
509 {
510     Network::setMyNick(mynick);
511     if (connectionState() == Network::Initializing)
512         networkInitialized();
513 }
514
515 void CoreNetwork::onSocketHasData()
516 {
517     while (socket.canReadLine()) {
518         QByteArray s = socket.readLine();
519         if (_metricsServer) {
520             _metricsServer->receiveDataNetwork(userId(), s.size());
521         }
522         if (s.endsWith("\r\n"))
523             s.chop(2);
524         else if (s.endsWith("\n"))
525             s.chop(1);
526         NetworkDataEvent* event = new NetworkDataEvent(EventManager::NetworkIncoming, this, s);
527         event->setTimestamp(QDateTime::currentDateTimeUtc());
528         emit newEvent(event);
529     }
530 }
531
532 void CoreNetwork::onSocketError(QAbstractSocket::SocketError error)
533 {
534     // Ignore socket closed errors if expected
535     if (_disconnectExpected && error == QAbstractSocket::RemoteHostClosedError) {
536         return;
537     }
538
539     _previousConnectionAttemptFailed = true;
540     qWarning() << qPrintable(tr("Could not connect to %1 (%2)").arg(networkName(), socket.errorString()));
541     emit connectionError(socket.errorString());
542     showMessage(NetworkInternalMessage(
543         Message::Error,
544         BufferInfo::StatusBuffer,
545         "",
546         tr("Connection failure: %1").arg(socket.errorString())
547     ));
548     emitConnectionError(socket.errorString());
549     if (socket.state() < QAbstractSocket::ConnectedState) {
550         onSocketDisconnected();
551     }
552 }
553
554 void CoreNetwork::onSocketInitialized()
555 {
556     CoreIdentity* identity = identityPtr();
557     if (!identity) {
558         qCritical() << "Identity invalid!";
559         disconnectFromIrc();
560         return;
561     }
562
563     Server server = usedServer();
564
565 #ifdef HAVE_SSL
566     // Non-SSL connections enter here only once, always emit socketInitialized(...) in these cases
567     // SSL connections call socketInitialized() twice, only emit socketInitialized(...) on the first (not yet encrypted) run
568     if (!server.useSsl || !socket.isEncrypted()) {
569         emit socketInitialized(identity, localAddress(), localPort(), peerAddress(), peerPort(), _socketId);
570     }
571
572     if (server.useSsl && !socket.isEncrypted()) {
573         // We'll finish setup once we're encrypted, and called again
574         return;
575     }
576 #else
577     emit socketInitialized(identity, localAddress(), localPort(), peerAddress(), peerPort(), _socketId);
578 #endif
579
580     socket.setSocketOption(QAbstractSocket::KeepAliveOption, true);
581
582     // Update the TokenBucket, force-enabling unlimited message rates for initial registration and
583     // capability negotiation.  networkInitialized() will call updateRateLimiting() without the
584     // force flag to apply user preferences.  When making changes, ensure that this still happens!
585     // As Quassel waits for CAP ACK/NAK and AUTHENTICATE replies, this shouldn't ever fill the IRC
586     // server receive queue and cause a kill.  "Shouldn't" being the operative word; the real world
587     // is a scary place.
588     updateRateLimiting(true);
589     // Fill up the token bucket as we're connecting from scratch
590     resetTokenBucket();
591
592     // Request capabilities as per IRCv3.2 specifications
593     // Older servers should ignore this; newer servers won't downgrade to RFC1459
594     showMessage(NetworkInternalMessage(
595         Message::Server,
596         BufferInfo::StatusBuffer,
597         "",
598         tr("Requesting capability list...")
599     ));
600     putRawLine(serverEncode(QString("CAP LS 302")));
601
602     if (!server.password.isEmpty()) {
603         putRawLine(serverEncode(QString("PASS %1").arg(server.password)));
604     }
605     QString nick;
606     if (identity->nicks().isEmpty()) {
607         nick = "quassel";
608         qWarning() << "CoreNetwork::socketInitialized(): no nicks supplied for identity Id" << identity->id();
609     }
610     else {
611         nick = identity->nicks()[0];
612     }
613     putRawLine(serverEncode(QString("NICK %1").arg(nick)));
614     // Only allow strict-compliant idents when strict mode is enabled
615     putRawLine(serverEncode(QString("USER %1 8 * :%2").arg(coreSession()->strictCompliantIdent(identity), identity->realName())));
616 }
617
618 void CoreNetwork::onSocketDisconnected()
619 {
620     disablePingTimeout();
621     _msgQueue.clear();
622     if (_metricsServer) {
623         _metricsServer->messageQueue(userId(), 0);
624     }
625
626     _autoWhoCycleTimer.stop();
627     _autoWhoTimer.stop();
628     _autoWhoQueue.clear();
629     _autoWhoPending.clear();
630
631     _socketCloseTimer.stop();
632
633     _tokenBucketTimer.stop();
634
635     IrcUser* me_ = me();
636     if (me_) {
637         for (const QString& channel : me_->channels()) {
638             showMessage(NetworkInternalMessage(
639                 Message::Quit,
640                 BufferInfo::ChannelBuffer,
641                 channel,
642                 _quitReason, me_->hostmask()
643             ));
644         }
645     }
646
647     setConnected(false);
648     emit disconnected(networkId());
649     emit socketDisconnected(identityPtr(), localAddress(), localPort(), peerAddress(), peerPort(), _socketId);
650     // Reset disconnect expectations
651     _disconnectExpected = false;
652     if (_quitRequested) {
653         _quitRequested = false;
654         setConnectionState(Network::Disconnected);
655         Core::setNetworkConnected(userId(), networkId(), false);
656     }
657     else if (_autoReconnectCount != 0) {
658         setConnectionState(Network::Reconnecting);
659         if (_autoReconnectCount == -1 || _autoReconnectCount == autoReconnectRetries())
660             doAutoReconnect();  // first try is immediate
661         else
662             _autoReconnectTimer.start();
663     }
664
665     if (_metricsServer) {
666         _metricsServer->removeNetwork(userId());
667     }
668 }
669
670 void CoreNetwork::onSocketStateChanged(QAbstractSocket::SocketState socketState)
671 {
672     Network::ConnectionState state;
673     switch (socketState) {
674     case QAbstractSocket::UnconnectedState:
675         state = Network::Disconnected;
676         onSocketDisconnected();
677         break;
678     case QAbstractSocket::HostLookupState:
679     case QAbstractSocket::ConnectingState:
680         state = Network::Connecting;
681         break;
682     case QAbstractSocket::ConnectedState:
683         state = Network::Initializing;
684         break;
685     case QAbstractSocket::ClosingState:
686         state = Network::Disconnecting;
687         break;
688     default:
689         state = Network::Disconnected;
690     }
691     setConnectionState(state);
692 }
693
694 void CoreNetwork::networkInitialized()
695 {
696     setConnectionState(Network::Initialized);
697     setConnected(true);
698     _disconnectExpected = false;
699     _quitRequested = false;
700
701     // Update the TokenBucket with specified rate-limiting settings, removing the force-unlimited
702     // flag used for initial registration and capability negotiation.
703     updateRateLimiting();
704
705     if (useAutoReconnect()) {
706         // reset counter
707         _autoReconnectCount = unlimitedReconnectRetries() ? -1 : autoReconnectRetries();
708     }
709
710     // restore away state
711     QString awayMsg = Core::awayMessage(userId(), networkId());
712     if (!awayMsg.isEmpty()) {
713         // Don't re-apply any timestamp formatting in order to preserve escaped percent signs, e.g.
714         // '%%%%%%%%' -> '%%%%'  If processed again, it'd result in '%%'.
715         userInputHandler()->handleAway(BufferInfo(), awayMsg, true);
716     }
717
718     sendPerform();
719
720     _sendPings = true;
721
722     if (networkConfig()->autoWhoEnabled()) {
723         _autoWhoCycleTimer.start();
724         _autoWhoTimer.start();
725         startAutoWhoCycle();  // FIXME wait for autojoin to be completed
726     }
727
728     Core::bufferInfo(userId(), networkId(), BufferInfo::StatusBuffer);  // create status buffer
729     Core::setNetworkConnected(userId(), networkId(), true);
730 }
731
732 void CoreNetwork::sendPerform()
733 {
734     BufferInfo statusBuf = BufferInfo::fakeStatusBuffer(networkId());
735
736     // do auto identify
737     if (useAutoIdentify() && !autoIdentifyService().isEmpty() && !autoIdentifyPassword().isEmpty()) {
738         userInputHandler()->handleMsg(statusBuf, QString("%1 IDENTIFY %2").arg(autoIdentifyService(), autoIdentifyPassword()));
739     }
740
741     // restore old user modes if server default mode is set.
742     IrcUser* me_ = me();
743     if (me_) {
744         if (!me_->userModes().isEmpty()) {
745             restoreUserModes();
746         }
747         else {
748             connect(me_, &IrcUser::userModesSet, this, &CoreNetwork::restoreUserModes);
749             connect(me_, &IrcUser::userModesAdded, this, &CoreNetwork::restoreUserModes);
750         }
751     }
752
753     // send perform list
754     for (const QString& line : perform()) {
755         if (!line.isEmpty())
756             userInput(statusBuf, line);
757     }
758
759     // rejoin channels we've been in
760     if (rejoinChannels()) {
761         QStringList channels, keys;
762         for (const QString& chan : coreSession()->persistentChannels(networkId()).keys()) {
763             QString key = channelKey(chan);
764             if (!key.isEmpty()) {
765                 channels.prepend(chan);
766                 keys.prepend(key);
767             }
768             else {
769                 channels.append(chan);
770             }
771         }
772         QString joinString = QString("%1 %2").arg(channels.join(",")).arg(keys.join(",")).trimmed();
773         if (!joinString.isEmpty())
774             userInputHandler()->handleJoin(statusBuf, joinString);
775     }
776 }
777
778 void CoreNetwork::restoreUserModes()
779 {
780     IrcUser* me_ = me();
781     Q_ASSERT(me_);
782
783     disconnect(me_, &IrcUser::userModesSet, this, &CoreNetwork::restoreUserModes);
784     disconnect(me_, &IrcUser::userModesAdded, this, &CoreNetwork::restoreUserModes);
785
786     QString modesDelta = Core::userModes(userId(), networkId());
787     QString currentModes = me_->userModes();
788
789     QString addModes, removeModes;
790     if (modesDelta.contains('-')) {
791         addModes = modesDelta.section('-', 0, 0);
792         removeModes = modesDelta.section('-', 1);
793     }
794     else {
795         addModes = modesDelta;
796     }
797
798     addModes.remove(QRegExp(QString("[%1]").arg(currentModes)));
799     if (currentModes.isEmpty())
800         removeModes = QString();
801     else
802         removeModes.remove(QRegExp(QString("[^%1]").arg(currentModes)));
803
804     if (addModes.isEmpty() && removeModes.isEmpty())
805         return;
806
807     if (!addModes.isEmpty())
808         addModes = '+' + addModes;
809     if (!removeModes.isEmpty())
810         removeModes = '-' + removeModes;
811
812     // don't use InputHandler::handleMode() as it keeps track of our persistent mode changes
813     putRawLine(serverEncode(QString("MODE %1 %2%3").arg(me_->nick()).arg(addModes).arg(removeModes)));
814 }
815
816 void CoreNetwork::updateIssuedModes(const QString& requestedModes)
817 {
818     QString addModes;
819     QString removeModes;
820     bool addMode = true;
821
822     for (auto requestedMode : requestedModes) {
823         if (requestedMode == '+') {
824             addMode = true;
825             continue;
826         }
827         if (requestedMode == '-') {
828             addMode = false;
829             continue;
830         }
831         if (addMode) {
832             addModes += requestedMode;
833         }
834         else {
835             removeModes += requestedMode;
836         }
837     }
838
839     QString addModesOld = _requestedUserModes.section('-', 0, 0);
840     QString removeModesOld = _requestedUserModes.section('-', 1);
841
842     addModes.remove(QRegExp(QString("[%1]").arg(addModesOld)));     // deduplicate
843     addModesOld.remove(QRegExp(QString("[%1]").arg(removeModes)));  // update
844     addModes += addModesOld;
845
846     removeModes.remove(QRegExp(QString("[%1]").arg(removeModesOld)));  // deduplicate
847     removeModesOld.remove(QRegExp(QString("[%1]").arg(addModes)));     // update
848     removeModes += removeModesOld;
849
850     _requestedUserModes = QString("%1-%2").arg(addModes).arg(removeModes);
851 }
852
853 void CoreNetwork::updatePersistentModes(QString addModes, QString removeModes)
854 {
855     QString persistentUserModes = Core::userModes(userId(), networkId());
856
857     QString requestedAdd = _requestedUserModes.section('-', 0, 0);
858     QString requestedRemove = _requestedUserModes.section('-', 1);
859
860     QString persistentAdd, persistentRemove;
861     if (persistentUserModes.contains('-')) {
862         persistentAdd = persistentUserModes.section('-', 0, 0);
863         persistentRemove = persistentUserModes.section('-', 1);
864     }
865     else {
866         persistentAdd = persistentUserModes;
867     }
868
869     // remove modes we didn't issue
870     if (requestedAdd.isEmpty())
871         addModes = QString();
872     else
873         addModes.remove(QRegExp(QString("[^%1]").arg(requestedAdd)));
874
875     if (requestedRemove.isEmpty())
876         removeModes = QString();
877     else
878         removeModes.remove(QRegExp(QString("[^%1]").arg(requestedRemove)));
879
880     // deduplicate
881     persistentAdd.remove(QRegExp(QString("[%1]").arg(addModes)));
882     persistentRemove.remove(QRegExp(QString("[%1]").arg(removeModes)));
883
884     // update
885     persistentAdd.remove(QRegExp(QString("[%1]").arg(removeModes)));
886     persistentRemove.remove(QRegExp(QString("[%1]").arg(addModes)));
887
888     // update issued mode list
889     requestedAdd.remove(QRegExp(QString("[%1]").arg(addModes)));
890     requestedRemove.remove(QRegExp(QString("[%1]").arg(removeModes)));
891     _requestedUserModes = QString("%1-%2").arg(requestedAdd).arg(requestedRemove);
892
893     persistentAdd += addModes;
894     persistentRemove += removeModes;
895     Core::setUserModes(userId(), networkId(), QString("%1-%2").arg(persistentAdd).arg(persistentRemove));
896 }
897
898 void CoreNetwork::resetPersistentModes()
899 {
900     _requestedUserModes = QString('-');
901     Core::setUserModes(userId(), networkId(), QString());
902 }
903
904 void CoreNetwork::setUseAutoReconnect(bool use)
905 {
906     Network::setUseAutoReconnect(use);
907     if (!use)
908         _autoReconnectTimer.stop();
909 }
910
911 void CoreNetwork::setAutoReconnectInterval(quint32 interval)
912 {
913     Network::setAutoReconnectInterval(interval);
914     _autoReconnectTimer.setInterval(interval * 1000);
915 }
916
917 void CoreNetwork::setAutoReconnectRetries(quint16 retries)
918 {
919     Network::setAutoReconnectRetries(retries);
920     if (_autoReconnectCount != 0) {
921         if (unlimitedReconnectRetries())
922             _autoReconnectCount = -1;
923         else
924             _autoReconnectCount = autoReconnectRetries();
925     }
926 }
927
928 void CoreNetwork::doAutoReconnect()
929 {
930     if (connectionState() != Network::Disconnected && connectionState() != Network::Reconnecting) {
931         qWarning() << "CoreNetwork::doAutoReconnect(): Cannot reconnect while not being disconnected!";
932         return;
933     }
934     if (_autoReconnectCount > 0 || _autoReconnectCount == -1)
935         _autoReconnectCount--;  // -2 means we delay the next reconnect
936     connectToIrc(true);
937 }
938
939 void CoreNetwork::sendPing()
940 {
941     qint64 now = QDateTime::currentDateTime().toMSecsSinceEpoch();
942     if (_pingCount != 0) {
943         qDebug() << "UserId:" << userId() << "Network:" << networkName() << "missed" << _pingCount << "pings."
944                  << "BA:" << socket.bytesAvailable() << "BTW:" << socket.bytesToWrite();
945     }
946     if ((int) _pingCount >= networkConfig()->maxPingCount() && (now - _lastPingTime) <= (_pingTimer.interval() + (1 * 1000))) {
947         // In transitioning to 64-bit time, the interval no longer needs converted down to seconds.
948         // However, to reduce the risk of breaking things by changing past behavior, we still allow
949         // up to 1 second missed instead of enforcing a stricter 1 millisecond allowance.
950         //
951         // the second check compares the actual elapsed time since the last ping and the pingTimer interval
952         // if the interval is shorter then the actual elapsed time it means that this thread was somehow blocked
953         // and unable to even handle a ping answer. So we ignore those misses.
954         disconnectFromIrc(false,
955                           QString("No Ping reply in %1 seconds.").arg(_pingCount * _pingTimer.interval() / 1000),
956                           true /* withReconnect */);
957     }
958     else {
959         _lastPingTime = now;
960         _pingCount++;
961         // Don't send pings until the network is initialized
962         if (_sendPings) {
963             // Mark as waiting for a reply
964             _pongReplyPending = true;
965             // Send default timestamp ping
966             userInputHandler()->handlePing(BufferInfo(), QString());
967         }
968     }
969 }
970
971 void CoreNetwork::enablePingTimeout(bool enable)
972 {
973     if (!enable)
974         disablePingTimeout();
975     else {
976         resetPingTimeout();
977         resetPongReplyPending();
978         if (networkConfig()->pingTimeoutEnabled())
979             _pingTimer.start();
980     }
981 }
982
983 void CoreNetwork::disablePingTimeout()
984 {
985     _pingTimer.stop();
986     _sendPings = false;
987     resetPingTimeout();
988     resetPongReplyPending();
989 }
990
991 void CoreNetwork::setPingInterval(int interval)
992 {
993     _pingTimer.setInterval(interval * 1000);
994 }
995
996 void CoreNetwork::setPongTimestampValid(bool validTimestamp)
997 {
998     _pongTimestampValid = validTimestamp;
999 }
1000
1001 /******** Custom Rate Limiting ********/
1002
1003 void CoreNetwork::updateRateLimiting(bool forceUnlimited)
1004 {
1005     // Verify and apply custom rate limiting options, always resetting the delay and burst size
1006     // (safe-guarding against accidentally starting the timer), but don't reset the token bucket as
1007     // this may be called while connected to a server.
1008
1009     if (useCustomMessageRate() || forceUnlimited) {
1010         // Custom message rates enabled, or chosen by means of forcing unlimited.  Let's go for it!
1011
1012         _messageDelay = messageRateDelay();
1013
1014         _burstSize = messageRateBurstSize();
1015         if (_burstSize < 1) {
1016             qWarning() << "Invalid messageRateBurstSize data, cannot have zero message burst size!" << _burstSize;
1017             // Can't go slower than one message at a time
1018             _burstSize = 1;
1019         }
1020
1021         if (_tokenBucket > _burstSize) {
1022             // Don't let the token bucket exceed the maximum
1023             _tokenBucket = _burstSize;
1024             // To fill up the token bucket, use resetRateLimiting().  Don't do that here, otherwise
1025             // changing the rate-limit settings while connected to a server will incorrectly reset
1026             // the token bucket.
1027         }
1028
1029         // Toggle the timer according to whether or not rate limiting is enabled
1030         // If we're here, either useCustomMessageRate or forceUnlimited is true.  Thus, the logic is
1031         // _skipMessageRates = ((useCustomMessageRate && unlimitedMessageRate) || forceUnlimited)
1032         // Override user preferences if called with force unlimited, only used during connect.
1033         _skipMessageRates = (unlimitedMessageRate() || forceUnlimited);
1034         if (_skipMessageRates) {
1035             // If the message queue already contains messages, they need sent before disabling the
1036             // timer.  Set the timer to a rapid pace and let it disable itself.
1037             if (!_msgQueue.isEmpty()) {
1038                 qDebug() << "Outgoing message queue contains messages while disabling rate "
1039                             "limiting.  Sending remaining queued messages...";
1040                 // Promptly run the timer again to clear the messages.  Rate limiting is disabled,
1041                 // so nothing should cause this to block.. in theory.  However, don't directly call
1042                 // fillBucketAndProcessQueue() in order to keep it on a separate thread.
1043                 //
1044                 // TODO If testing shows this isn't needed, it can be simplified to a direct call.
1045                 // Hesitant to change it without a wide variety of situations to verify behavior.
1046                 _tokenBucketTimer.start(100);
1047             }
1048             else {
1049                 // No rate limiting, disable the timer
1050                 _tokenBucketTimer.stop();
1051             }
1052         }
1053         else {
1054             // Rate limiting enabled, enable the timer
1055             _tokenBucketTimer.start(_messageDelay);
1056         }
1057     }
1058     else {
1059         // Custom message rates disabled.  Go for the default.
1060
1061         _skipMessageRates = false;  // Enable rate-limiting by default
1062         _messageDelay = 2200;       // This seems to be a safe value (2.2 seconds delay)
1063         _burstSize = 5;             // 5 messages at once
1064         if (_tokenBucket > _burstSize) {
1065             // TokenBucket to avoid sending too much at once.  Don't let the token bucket exceed the
1066             // maximum.
1067             _tokenBucket = _burstSize;
1068             // To fill up the token bucket, use resetRateLimiting().  Don't do that here, otherwise
1069             // changing the rate-limit settings while connected to a server will incorrectly reset
1070             // the token bucket.
1071         }
1072         // Rate limiting enabled, enable the timer
1073         _tokenBucketTimer.start(_messageDelay);
1074     }
1075 }
1076
1077 void CoreNetwork::resetTokenBucket()
1078 {
1079     // Fill up the token bucket to the maximum
1080     _tokenBucket = _burstSize;
1081 }
1082
1083 /******** IRCv3 Capability Negotiation ********/
1084
1085 void CoreNetwork::serverCapAdded(const QString& capability)
1086 {
1087     // Check if it's a known capability; if so, add it to the list
1088     // Handle special cases first
1089     if (capability == IrcCap::SASL) {
1090         // Only request SASL if it's enabled
1091         if (networkInfo().useSasl)
1092             queueCap(capability);
1093     }
1094     else if (IrcCap::knownCaps.contains(capability)) {
1095         // Handling for general known capabilities
1096         queueCap(capability);
1097     }
1098 }
1099
1100 void CoreNetwork::serverCapAcknowledged(const QString& capability)
1101 {
1102     // This may be called multiple times in certain situations.
1103
1104     // Handle core-side configuration
1105     if (capability == IrcCap::AWAY_NOTIFY) {
1106         // away-notify enabled, stop the autoWho timers, handle manually
1107         setAutoWhoEnabled(false);
1108     }
1109
1110     // Handle capabilities that require further messages sent to the IRC server
1111     // If you change this list, ALSO change the list in CoreNetwork::capsRequiringServerMessages
1112     if (capability == IrcCap::SASL) {
1113         // If SASL mechanisms specified, limit to what's accepted for authentication
1114         // if the current identity has a cert set, use SASL EXTERNAL
1115         // FIXME use event
1116 #ifdef HAVE_SSL
1117         if (!identityPtr()->sslCert().isNull()) {
1118             if (saslMaybeSupports(IrcCap::SaslMech::EXTERNAL)) {
1119                 // EXTERNAL authentication supported, send request
1120                 putRawLine(serverEncode("AUTHENTICATE EXTERNAL"));
1121             }
1122             else {
1123                 showMessage(NetworkInternalMessage(
1124                     Message::Error,
1125                     BufferInfo::StatusBuffer,
1126                     "",
1127                     tr("SASL EXTERNAL authentication not supported")
1128                 ));
1129                 sendNextCap();
1130             }
1131         }
1132         else {
1133 #endif
1134             if (saslMaybeSupports(IrcCap::SaslMech::PLAIN)) {
1135                 // PLAIN authentication supported, send request
1136                 // Only working with PLAIN atm, blowfish later
1137                 putRawLine(serverEncode("AUTHENTICATE PLAIN"));
1138             }
1139             else {
1140                 showMessage(NetworkInternalMessage(
1141                     Message::Error,
1142                     BufferInfo::StatusBuffer,
1143                     "",
1144                     tr("SASL PLAIN authentication not supported")
1145                 ));
1146                 sendNextCap();
1147             }
1148 #ifdef HAVE_SSL
1149         }
1150 #endif
1151     }
1152 }
1153
1154 void CoreNetwork::serverCapRemoved(const QString& capability)
1155 {
1156     // This may be called multiple times in certain situations.
1157
1158     // Handle special cases here
1159     if (capability == IrcCap::AWAY_NOTIFY) {
1160         // away-notify disabled, enable autoWho according to configuration
1161         setAutoWhoEnabled(networkConfig()->autoWhoEnabled());
1162     }
1163 }
1164
1165 void CoreNetwork::queueCap(const QString& capability)
1166 {
1167     // IRCv3 specs all use lowercase capability names
1168     QString _capLowercase = capability.toLower();
1169
1170     if (capsRequiringConfiguration.contains(_capLowercase)) {
1171         // The capability requires additional configuration before being acknowledged (e.g. SASL),
1172         // so we should negotiate it separately from all other capabilities.  Otherwise new
1173         // capabilities will be requested while still configuring the previous one.
1174         if (!_capsQueuedIndividual.contains(_capLowercase)) {
1175             _capsQueuedIndividual.append(_capLowercase);
1176         }
1177     }
1178     else {
1179         // The capability doesn't need any special configuration, so it should be safe to try
1180         // bundling together with others.  "Should" being the imperative word, as IRC servers can do
1181         // anything.
1182         if (!_capsQueuedBundled.contains(_capLowercase)) {
1183             _capsQueuedBundled.append(_capLowercase);
1184         }
1185     }
1186 }
1187
1188 QString CoreNetwork::takeQueuedCaps()
1189 {
1190     // Clear the record of the most recently negotiated capability bundle.  Does nothing if the list
1191     // is empty.
1192     _capsQueuedLastBundle.clear();
1193
1194     // First, negotiate all the standalone capabilities that require additional configuration.
1195     if (!_capsQueuedIndividual.empty()) {
1196         // We have an individual capability available.  Take the first and pass it back.
1197         return _capsQueuedIndividual.takeFirst();
1198     }
1199     else if (!_capsQueuedBundled.empty()) {
1200         // We have capabilities available that can be grouped.  Try to fit in as many as within the
1201         // maximum length.
1202         // See CoreNetwork::maxCapRequestLength
1203
1204         // Response must have at least one capability regardless of max length for anything to
1205         // happen.
1206         QString capBundle = _capsQueuedBundled.takeFirst();
1207         QString nextCap("");
1208         while (!_capsQueuedBundled.empty()) {
1209             // As long as capabilities remain, get the next...
1210             nextCap = _capsQueuedBundled.first();
1211             if ((capBundle.length() + 1 + nextCap.length()) <= maxCapRequestLength) {
1212                 // [capability + 1 for a space + this new capability] fit within length limits
1213                 // Add it to formatted list
1214                 capBundle.append(" " + nextCap);
1215                 // Add it to most recent bundle of requested capabilities (simplifies retry logic)
1216                 _capsQueuedLastBundle.append(nextCap);
1217                 // Then remove it from the queue
1218                 _capsQueuedBundled.removeFirst();
1219             }
1220             else {
1221                 // We've reached the length limit for a single capability request, stop adding more
1222                 break;
1223             }
1224         }
1225         // Return this space-separated set of capabilities, removing any extra spaces
1226         return capBundle.trimmed();
1227     }
1228     else {
1229         // No capabilities left to negotiate, return an empty string.
1230         return QString();
1231     }
1232 }
1233
1234 void CoreNetwork::retryCapsIndividually()
1235 {
1236     // The most recent set of capabilities got denied by the IRC server.  As we don't know what got
1237     // denied, try each capability individually.
1238     if (_capsQueuedLastBundle.empty()) {
1239         // No most recently tried capability set, just return.
1240         return;
1241         // Note: there's little point in retrying individually requested caps during negotiation.
1242         // We know the individual capability was the one that failed, and it's not likely it'll
1243         // suddenly start working within a few seconds.  'cap-notify' provides a better system for
1244         // handling capability removal and addition.
1245     }
1246
1247     // This should be fairly rare, e.g. services restarting during negotiation, so simplicity wins
1248     // over efficiency.  If this becomes an issue, implement a binary splicing system instead,
1249     // keeping track of which halves of the group fail, dividing the set each time.
1250
1251     // Add most recently tried capability set to individual list, re-requesting them one at a time
1252     _capsQueuedIndividual.append(_capsQueuedLastBundle);
1253     // Warn of this issue to explain the slower login.  Servers usually shouldn't trigger this.
1254     showMessage(NetworkInternalMessage(
1255         Message::Server,
1256         BufferInfo::StatusBuffer,
1257         "",
1258         tr("Could not negotiate some capabilities, retrying individually (%1)...").arg(_capsQueuedLastBundle.join(", "))
1259     ));
1260     // Capabilities are already removed from the capability bundle queue via takeQueuedCaps(), no
1261     // need to remove them here.
1262     // Clear the most recently tried set to reduce risk that mistakes elsewhere causes retrying
1263     // indefinitely.
1264     _capsQueuedLastBundle.clear();
1265 }
1266
1267 void CoreNetwork::beginCapNegotiation()
1268 {
1269     // Don't begin negotiation if no capabilities are queued to request
1270     if (!capNegotiationInProgress()) {
1271         // If the server doesn't have any capabilities, but supports CAP LS, continue on with the
1272         // normal connection.
1273         showMessage(NetworkInternalMessage(
1274             Message::Server,
1275             BufferInfo::StatusBuffer,
1276             "",
1277             tr("No capabilities available")
1278         ));
1279         endCapNegotiation();
1280         return;
1281     }
1282
1283     _capNegotiationActive = true;
1284     showMessage(NetworkInternalMessage(
1285         Message::Server,
1286         BufferInfo::StatusBuffer,
1287         "",
1288         tr("Ready to negotiate (found: %1)").arg(caps().join(", "))
1289     ));
1290
1291     // Build a list of queued capabilities, starting with individual, then bundled, only adding the
1292     // comma separator between the two if needed (both individual and bundled caps exist).
1293     QString queuedCapsDisplay = _capsQueuedIndividual.join(", ")
1294                                 + ((!_capsQueuedIndividual.empty() && !_capsQueuedBundled.empty()) ? ", " : "")
1295                                 + _capsQueuedBundled.join(", ");
1296     showMessage(NetworkInternalMessage(
1297         Message::Server,
1298         BufferInfo::StatusBuffer,
1299         "",
1300         tr("Negotiating capabilities (requesting: %1)...").arg(queuedCapsDisplay)
1301     ));
1302
1303     sendNextCap();
1304 }
1305
1306 void CoreNetwork::sendNextCap()
1307 {
1308     if (capNegotiationInProgress()) {
1309         // Request the next set of capabilities and remove them from the list
1310         putRawLine(serverEncode(QString("CAP REQ :%1").arg(takeQueuedCaps())));
1311     }
1312     else {
1313         // No pending desired capabilities, capability negotiation finished
1314         // If SASL requested but not available, print a warning
1315         if (networkInfo().useSasl && !capEnabled(IrcCap::SASL))
1316             showMessage(NetworkInternalMessage(
1317                 Message::Error,
1318                 BufferInfo::StatusBuffer,
1319                 "",
1320                 tr("SASL authentication currently not supported by server")
1321             ));
1322
1323         if (_capNegotiationActive) {
1324             showMessage(NetworkInternalMessage(
1325                 Message::Server,
1326                 BufferInfo::StatusBuffer,
1327                 "",
1328                 tr("Capability negotiation finished (enabled: %1)").arg(capsEnabled().join(", "))
1329             ));
1330             _capNegotiationActive = false;
1331         }
1332
1333         endCapNegotiation();
1334     }
1335 }
1336
1337 void CoreNetwork::endCapNegotiation()
1338 {
1339     // If nick registration is already complete, CAP END is not required
1340     if (!_capInitialNegotiationEnded) {
1341         putRawLine(serverEncode(QString("CAP END")));
1342         _capInitialNegotiationEnded = true;
1343     }
1344 }
1345
1346 /******** AutoWHO ********/
1347
1348 void CoreNetwork::startAutoWhoCycle()
1349 {
1350     if (!_autoWhoQueue.isEmpty()) {
1351         _autoWhoCycleTimer.stop();
1352         return;
1353     }
1354     _autoWhoQueue = channels();
1355 }
1356
1357 void CoreNetwork::queueAutoWhoOneshot(const QString& name)
1358 {
1359     // Prepend so these new channels/nicks are the first to be checked
1360     // Don't allow duplicates
1361     if (!_autoWhoQueue.contains(name.toLower())) {
1362         _autoWhoQueue.prepend(name.toLower());
1363     }
1364     if (capEnabled(IrcCap::AWAY_NOTIFY)) {
1365         // When away-notify is active, the timer's stopped.  Start a new cycle to who this channel.
1366         setAutoWhoEnabled(true);
1367     }
1368 }
1369
1370 void CoreNetwork::setAutoWhoDelay(int delay)
1371 {
1372     _autoWhoTimer.setInterval(delay * 1000);
1373 }
1374
1375 void CoreNetwork::setAutoWhoInterval(int interval)
1376 {
1377     _autoWhoCycleTimer.setInterval(interval * 1000);
1378 }
1379
1380 void CoreNetwork::setAutoWhoEnabled(bool enabled)
1381 {
1382     if (enabled && isConnected() && !_autoWhoTimer.isActive())
1383         _autoWhoTimer.start();
1384     else if (!enabled) {
1385         _autoWhoTimer.stop();
1386         _autoWhoCycleTimer.stop();
1387     }
1388 }
1389
1390 void CoreNetwork::sendAutoWho()
1391 {
1392     // Don't send autowho if there are still some pending
1393     if (_autoWhoPending.count())
1394         return;
1395
1396     while (!_autoWhoQueue.isEmpty()) {
1397         QString chanOrNick = _autoWhoQueue.takeFirst();
1398         // Check if it's a known channel or nick
1399         IrcChannel* ircchan = ircChannel(chanOrNick);
1400         IrcUser* ircuser = ircUser(chanOrNick);
1401         if (ircchan) {
1402             // Apply channel limiting rules
1403             // If using away-notify, don't impose channel size limits in order to capture away
1404             // state of everyone.  Auto-who won't run on a timer so network impact is minimal.
1405             if (networkConfig()->autoWhoNickLimit() > 0 && ircchan->ircUsers().count() >= networkConfig()->autoWhoNickLimit()
1406                 && !capEnabled(IrcCap::AWAY_NOTIFY))
1407                 continue;
1408             _autoWhoPending[chanOrNick.toLower()]++;
1409         }
1410         else if (ircuser) {
1411             // Checking a nick, add it to the pending list
1412             _autoWhoPending[ircuser->nick().toLower()]++;
1413         }
1414         else {
1415             // Not a channel or a nick, skip it
1416             qDebug() << "Skipping who polling of unknown channel or nick" << chanOrNick;
1417             continue;
1418         }
1419         if (supports("WHOX")) {
1420             // Use WHO extended to poll away users and/or user accounts
1421             // Explicitly only match on nickname ("n"), don't rely on server defaults
1422             //
1423             // WHO <nickname> n%chtsunfra,<unique_number>
1424             //
1425             // See http://faerion.sourceforge.net/doc/irc/whox.var
1426             // And https://github.com/quakenet/snircd/blob/master/doc/readme.who
1427             // And https://github.com/hexchat/hexchat/blob/57478b65758e6b697b1d82ce21075e74aa475efc/src/common/proto-irc.c#L752
1428             putRawLine(serverEncode(
1429                 QString("WHO %1 n%chtsunfra,%2")
1430                     .arg(chanOrNick, QString::number(IrcCap::ACCOUNT_NOTIFY_WHOX_NUM))
1431             ));
1432         }
1433         else {
1434             // Fall back to normal WHO
1435             //
1436             // Note: According to RFC 1459, "WHO <phrase>" can fall back to searching realname,
1437             // hostmask, etc.  There's nothing we can do about that :(
1438             //
1439             // See https://tools.ietf.org/html/rfc1459#section-4.5.1
1440             putRawLine(serverEncode(QString("WHO %1").arg(chanOrNick)));
1441         }
1442         break;
1443     }
1444
1445     if (_autoWhoQueue.isEmpty() && networkConfig()->autoWhoEnabled() && !_autoWhoCycleTimer.isActive() && !capEnabled(IrcCap::AWAY_NOTIFY)) {
1446         // Timer was stopped, means a new cycle is due immediately
1447         // Don't run a new cycle if using away-notify; server will notify as appropriate
1448         _autoWhoCycleTimer.start();
1449         startAutoWhoCycle();
1450     }
1451     else if (capEnabled(IrcCap::AWAY_NOTIFY) && _autoWhoCycleTimer.isActive()) {
1452         // Don't run another who cycle if away-notify is enabled
1453         _autoWhoCycleTimer.stop();
1454     }
1455 }
1456
1457 #ifdef HAVE_SSL
1458 void CoreNetwork::onSslErrors(const QList<QSslError>& sslErrors)
1459 {
1460     Server server = usedServer();
1461     if (server.sslVerify) {
1462         // Treat the SSL error as a hard error
1463         QString sslErrorMessage = tr("Encrypted connection couldn't be verified, disconnecting "
1464                                      "since verification is required");
1465         if (!sslErrors.empty()) {
1466             // Add the error reason if known
1467             sslErrorMessage.append(tr(" (Reason: %1)").arg(sslErrors.first().errorString()));
1468         }
1469         showMessage(NetworkInternalMessage(
1470             Message::Error,
1471             BufferInfo::StatusBuffer,
1472             "",
1473             sslErrorMessage
1474         ));
1475
1476         // Disconnect, triggering a reconnect in case it's a temporary issue with certificate
1477         // validity, network trouble, etc.
1478         disconnectFromIrc(false, QString("Encrypted connection not verified"), true /* withReconnect */);
1479     }
1480     else {
1481         // Treat the SSL error as a warning, continue to connect anyways
1482         QString sslErrorMessage = tr("Encrypted connection couldn't be verified, continuing "
1483                                      "since verification is not required");
1484         if (!sslErrors.empty()) {
1485             // Add the error reason if known
1486             sslErrorMessage.append(tr(" (Reason: %1)").arg(sslErrors.first().errorString()));
1487         }
1488         showMessage(NetworkInternalMessage(
1489             Message::Info,
1490             BufferInfo::StatusBuffer,
1491             "",
1492             sslErrorMessage
1493         ));
1494
1495         // Proceed with the connection
1496         socket.ignoreSslErrors();
1497     }
1498 }
1499
1500 #endif  // HAVE_SSL
1501
1502 void CoreNetwork::checkTokenBucket()
1503 {
1504     if (_skipMessageRates) {
1505         if (_msgQueue.empty()) {
1506             // Message queue emptied; stop the timer and bail out
1507             _tokenBucketTimer.stop();
1508             return;
1509         }
1510         // Otherwise, we're emptying the queue, continue on as normal
1511     }
1512
1513     // Process whatever messages are pending
1514     fillBucketAndProcessQueue();
1515 }
1516
1517 void CoreNetwork::fillBucketAndProcessQueue()
1518 {
1519     // If there's less tokens than burst size, refill the token bucket by 1
1520     if (_tokenBucket < _burstSize) {
1521         _tokenBucket++;
1522     }
1523
1524     // As long as there's tokens available and messages remaining, sending messages from the queue
1525     while (!_msgQueue.empty() && _tokenBucket > 0) {
1526         writeToSocket(_msgQueue.takeFirst());
1527         if (_metricsServer) {
1528             _metricsServer->messageQueue(userId(), _msgQueue.size());
1529         }
1530     }
1531 }
1532
1533 void CoreNetwork::writeToSocket(const QByteArray& data)
1534 {
1535     // Log the message if enabled and network ID matches or allows all
1536     if (_debugLogRawIrc && (_debugLogRawNetId == -1 || networkId().toInt() == _debugLogRawNetId)) {
1537         // Include network ID
1538         qDebug() << "IRC net" << networkId() << ">>" << data;
1539     }
1540     socket.write(data);
1541     socket.write("\r\n");
1542     if (_metricsServer) {
1543         _metricsServer->transmitDataNetwork(userId(), data.size() + 2);
1544     }
1545     if (!_skipMessageRates) {
1546         // Only subtract from the token bucket if message rate limiting is enabled
1547         _tokenBucket--;
1548     }
1549 }
1550
1551 Network::Server CoreNetwork::usedServer() const
1552 {
1553     if (_lastUsedServerIndex < serverList().count())
1554         return serverList()[_lastUsedServerIndex];
1555
1556     if (!serverList().isEmpty())
1557         return serverList()[0];
1558
1559     return Network::Server();
1560 }
1561
1562 void CoreNetwork::requestConnect() const
1563 {
1564     if (_shuttingDown) {
1565         return;
1566     }
1567     if (connectionState() != Disconnected) {
1568         qWarning() << "Requesting connect while already being connected!";
1569         return;
1570     }
1571     QMetaObject::invokeMethod(const_cast<CoreNetwork*>(this), "connectToIrc", Qt::QueuedConnection);
1572 }
1573
1574 void CoreNetwork::requestDisconnect() const
1575 {
1576     if (_shuttingDown) {
1577         return;
1578     }
1579     if (connectionState() == Disconnected) {
1580         qWarning() << "Requesting disconnect while not being connected!";
1581         return;
1582     }
1583     userInputHandler()->handleQuit(BufferInfo(), QString());
1584 }
1585
1586 void CoreNetwork::requestSetNetworkInfo(const NetworkInfo& info)
1587 {
1588     Network::Server currentServer = usedServer();
1589     setNetworkInfo(info);
1590     Core::updateNetwork(coreSession()->user(), info);
1591
1592     // the order of the servers might have changed,
1593     // so we try to find the previously used server
1594     _lastUsedServerIndex = 0;
1595     for (int i = 0; i < serverList().count(); i++) {
1596         Network::Server server = serverList()[i];
1597         if (server.host == currentServer.host && server.port == currentServer.port) {
1598             _lastUsedServerIndex = i;
1599             break;
1600         }
1601     }
1602 }
1603
1604 QList<QList<QByteArray>> CoreNetwork::splitMessage(const QString& cmd,
1605                                                    const QString& message,
1606                                                    const std::function<QList<QByteArray>(QString&)>& cmdGenerator)
1607 {
1608     QString wrkMsg(message);
1609     QList<QList<QByteArray>> msgsToSend;
1610
1611     // do while (wrkMsg.size() > 0)
1612     do {
1613         // First, check to see if the whole message can be sent at once.  The
1614         // cmdGenerator function is passed in by the caller and is used to encode
1615         // and encrypt (if applicable) the message, since different callers might
1616         // want to use different encoding or encode different values.
1617         int splitPos = wrkMsg.size();
1618         QList<QByteArray> initialSplitMsgEnc = cmdGenerator(wrkMsg);
1619         int initialOverrun = userInputHandler()->lastParamOverrun(cmd, initialSplitMsgEnc);
1620
1621         if (initialOverrun) {
1622             // If the message was too long to be sent, first try splitting it along
1623             // word boundaries with QTextBoundaryFinder.
1624             QString splitMsg(wrkMsg);
1625             QTextBoundaryFinder qtbf(QTextBoundaryFinder::Word, splitMsg);
1626             qtbf.setPosition(initialSplitMsgEnc[1].size() - initialOverrun);
1627             QList<QByteArray> splitMsgEnc;
1628             int overrun = initialOverrun;
1629
1630             while (overrun) {
1631                 splitPos = qtbf.toPreviousBoundary();
1632
1633                 // splitPos==-1 means the QTBF couldn't find a split point at all and
1634                 // splitPos==0 means the QTBF could only find a boundary at the beginning of
1635                 // the string.  Neither one of these works for us.
1636                 if (splitPos > 0) {
1637                     // If a split point could be found, split the message there, calculate the
1638                     // overrun, and continue with the loop.
1639                     splitMsg = splitMsg.left(splitPos);
1640                     splitMsgEnc = cmdGenerator(splitMsg);
1641                     overrun = userInputHandler()->lastParamOverrun(cmd, splitMsgEnc);
1642                 }
1643                 else {
1644                     // If a split point could not be found (the beginning of the message
1645                     // is reached without finding a split point short enough to send) and we
1646                     // are still in Word mode, switch to Grapheme mode.  We also need to restore
1647                     // the full wrkMsg to splitMsg, since splitMsg may have been cut down during
1648                     // the previous attempt to find a split point.
1649                     if (qtbf.type() == QTextBoundaryFinder::Word) {
1650                         splitMsg = wrkMsg;
1651                         splitPos = splitMsg.size();
1652                         QTextBoundaryFinder graphemeQtbf(QTextBoundaryFinder::Grapheme, splitMsg);
1653                         graphemeQtbf.setPosition(initialSplitMsgEnc[1].size() - initialOverrun);
1654                         qtbf = graphemeQtbf;
1655                     }
1656                     else {
1657                         // If the QTBF fails to find a split point in Grapheme mode, we give up.
1658                         // This should never happen, but it should be handled anyway.
1659                         qWarning() << "Unexpected failure to split message!";
1660                         return msgsToSend;
1661                     }
1662                 }
1663             }
1664
1665             // Once a message of sendable length has been found, remove it from the wrkMsg and
1666             // add it to the list of messages to be sent.
1667             wrkMsg.remove(0, splitPos);
1668             msgsToSend.append(splitMsgEnc);
1669         }
1670         else {
1671             // If the entire remaining message is short enough to be sent all at once, remove
1672             // it from the wrkMsg and add it to the list of messages to be sent.
1673             wrkMsg.remove(0, splitPos);
1674             msgsToSend.append(initialSplitMsgEnc);
1675         }
1676     } while (wrkMsg.size() > 0);
1677
1678     return msgsToSend;
1679 }