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