* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
***************************************************************************/
-#include <QHostInfo>
-
#include "corenetwork.h"
+#include <QDebug>
+#include <QHostInfo>
+
#include "core.h"
#include "coreidentity.h"
#include "corenetworkconfig.h"
// IRCv3 capabilities
#include "irccap.h"
-INIT_SYNCABLE_OBJECT(CoreNetwork)
CoreNetwork::CoreNetwork(const NetworkId &networkid, CoreSession *session)
: Network(networkid, session),
_coreSession(session),
_previousConnectionAttemptFailed(false),
_lastUsedServerIndex(0),
- _lastPingTime(0),
- _pingCount(0),
- _sendPings(false),
_requestedUserModes('-')
{
+ // Check if raw IRC logging is enabled
+ _debugLogRawIrc = (Quassel::isOptionSet("debug-irc") || Quassel::isOptionSet("debug-irc-id"));
+ _debugLogRawNetId = Quassel::optionValue("debug-irc-id").toInt();
+
_autoReconnectTimer.setSingleShot(true);
- connect(&_socketCloseTimer, SIGNAL(timeout()), this, SLOT(socketCloseTimeout()));
+ connect(&_socketCloseTimer, &QTimer::timeout, this, &CoreNetwork::onSocketCloseTimeout);
setPingInterval(networkConfig()->pingInterval());
- connect(&_pingTimer, SIGNAL(timeout()), this, SLOT(sendPing()));
+ connect(&_pingTimer, &QTimer::timeout, this, &CoreNetwork::sendPing);
setAutoWhoDelay(networkConfig()->autoWhoDelay());
setAutoWhoInterval(networkConfig()->autoWhoInterval());
storeChannelCipherKey(buffer.toLower(), bufferCiphers[buffer]);
}
- connect(networkConfig(), SIGNAL(pingTimeoutEnabledSet(bool)), SLOT(enablePingTimeout(bool)));
- connect(networkConfig(), SIGNAL(pingIntervalSet(int)), SLOT(setPingInterval(int)));
- connect(networkConfig(), SIGNAL(autoWhoEnabledSet(bool)), SLOT(setAutoWhoEnabled(bool)));
- connect(networkConfig(), SIGNAL(autoWhoIntervalSet(int)), SLOT(setAutoWhoInterval(int)));
- connect(networkConfig(), SIGNAL(autoWhoDelaySet(int)), SLOT(setAutoWhoDelay(int)));
+ connect(networkConfig(), &NetworkConfig::pingTimeoutEnabledSet, this, &CoreNetwork::enablePingTimeout);
+ connect(networkConfig(), &NetworkConfig::pingIntervalSet, this, &CoreNetwork::setPingInterval);
+ connect(networkConfig(), &NetworkConfig::autoWhoEnabledSet, this, &CoreNetwork::setAutoWhoEnabled);
+ connect(networkConfig(), &NetworkConfig::autoWhoIntervalSet, this, &CoreNetwork::setAutoWhoInterval);
+ connect(networkConfig(), &NetworkConfig::autoWhoDelaySet, this, &CoreNetwork::setAutoWhoDelay);
- connect(&_autoReconnectTimer, SIGNAL(timeout()), this, SLOT(doAutoReconnect()));
- connect(&_autoWhoTimer, SIGNAL(timeout()), this, SLOT(sendAutoWho()));
- connect(&_autoWhoCycleTimer, SIGNAL(timeout()), this, SLOT(startAutoWhoCycle()));
- connect(&_tokenBucketTimer, SIGNAL(timeout()), this, SLOT(checkTokenBucket()));
+ connect(&_autoReconnectTimer, &QTimer::timeout, this, &CoreNetwork::doAutoReconnect);
+ connect(&_autoWhoTimer, &QTimer::timeout, this, &CoreNetwork::sendAutoWho);
+ connect(&_autoWhoCycleTimer, &QTimer::timeout, this, &CoreNetwork::startAutoWhoCycle);
+ connect(&_tokenBucketTimer, &QTimer::timeout, this, &CoreNetwork::checkTokenBucket);
- connect(&socket, SIGNAL(connected()), this, SLOT(socketInitialized()));
- connect(&socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError)));
- connect(&socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(socketStateChanged(QAbstractSocket::SocketState)));
- connect(&socket, SIGNAL(readyRead()), this, SLOT(socketHasData()));
+ connect(&socket, &QAbstractSocket::connected, this, &CoreNetwork::onSocketInitialized);
+ connect(&socket, selectOverload<QAbstractSocket::SocketError>(&QAbstractSocket::error), this, &CoreNetwork::onSocketError);
+ connect(&socket, &QAbstractSocket::stateChanged, this, &CoreNetwork::onSocketStateChanged);
+ connect(&socket, &QIODevice::readyRead, this, &CoreNetwork::onSocketHasData);
#ifdef HAVE_SSL
- connect(&socket, SIGNAL(encrypted()), this, SLOT(socketInitialized()));
- connect(&socket, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(sslErrors(const QList<QSslError> &)));
+ connect(&socket, &QSslSocket::encrypted, this, &CoreNetwork::onSocketInitialized);
+ connect(&socket, selectOverload<const QList<QSslError>&>(&QSslSocket::sslErrors), this, &CoreNetwork::onSslErrors);
#endif
- connect(this, SIGNAL(newEvent(Event *)), coreSession()->eventManager(), SLOT(postEvent(Event *)));
+ connect(this, &CoreNetwork::newEvent, coreSession()->eventManager(), &EventManager::postEvent);
// Custom rate limiting
// These react to the user changing settings in the client
- connect(this, SIGNAL(useCustomMessageRateSet(bool)), SLOT(updateRateLimiting()));
- connect(this, SIGNAL(messageRateBurstSizeSet(quint32)), SLOT(updateRateLimiting()));
- connect(this, SIGNAL(messageRateDelaySet(quint32)), SLOT(updateRateLimiting()));
- connect(this, SIGNAL(unlimitedMessageRateSet(bool)), SLOT(updateRateLimiting()));
+ connect(this, &Network::useCustomMessageRateSet, this, &CoreNetwork::updateRateLimiting);
+ connect(this, &Network::messageRateBurstSizeSet, this, &CoreNetwork::updateRateLimiting);
+ connect(this, &Network::messageRateDelaySet, this, &CoreNetwork::updateRateLimiting);
+ connect(this, &Network::unlimitedMessageRateSet, this, &CoreNetwork::updateRateLimiting);
// IRCv3 capability handling
// These react to CAP messages from the server
- connect(this, SIGNAL(capAdded(QString)), this, SLOT(serverCapAdded(QString)));
- connect(this, SIGNAL(capAcknowledged(QString)), this, SLOT(serverCapAcknowledged(QString)));
- connect(this, SIGNAL(capRemoved(QString)), this, SLOT(serverCapRemoved(QString)));
+ connect(this, &Network::capAdded, this, &CoreNetwork::serverCapAdded);
+ connect(this, &Network::capAcknowledged, this, &CoreNetwork::serverCapAcknowledged);
+ connect(this, &Network::capRemoved, this, &CoreNetwork::serverCapRemoved);
if (Quassel::isOptionSet("oidentd")) {
- connect(this, SIGNAL(socketInitialized(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)), Core::instance()->oidentdConfigGenerator(), SLOT(addSocket(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)), Qt::BlockingQueuedConnection);
- connect(this, SIGNAL(socketDisconnected(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)), Core::instance()->oidentdConfigGenerator(), SLOT(removeSocket(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)));
+ connect(this, &CoreNetwork::socketInitialized, Core::instance()->oidentdConfigGenerator(), &OidentdConfigGenerator::addSocket);
+ connect(this, &CoreNetwork::socketDisconnected, Core::instance()->oidentdConfigGenerator(), &OidentdConfigGenerator::removeSocket);
+ }
+
+ if (Quassel::isOptionSet("ident-daemon")) {
+ connect(this, &CoreNetwork::socketInitialized, Core::instance()->identServer(), &IdentServer::addSocket);
+ connect(this, &CoreNetwork::socketDisconnected, Core::instance()->identServer(), &IdentServer::removeSocket);
}
}
CoreNetwork::~CoreNetwork()
{
- // Request a proper disconnect, but don't count as user-requested disconnect
- if (socketConnected()) {
- // Only try if the socket's fully connected (not initializing or disconnecting).
- // Force an immediate disconnect, jumping the command queue. Ensures the proper QUIT is
- // shown even if other messages are queued.
- disconnectFromIrc(false, QString(), false, true);
- // Process the putCmd events that trigger the quit. Without this, shutting down the core
- // results in abrubtly closing the socket rather than sending the QUIT as expected.
- QCoreApplication::processEvents();
- // Wait briefly for each network to disconnect. Sometimes it takes a little while to send.
- if (!forceDisconnect()) {
- qWarning() << "Timed out quitting network" << networkName() <<
- "(user ID " << userId() << ")";
- }
+ // Ensure we don't get any more signals from the socket while shutting down
+ disconnect(&socket, nullptr, this, nullptr);
+ if (!forceDisconnect()) {
+ qWarning() << QString{"Could not disconnect from network %1 (network ID: %2, user ID: %3)"}
+ .arg(networkName()).arg(networkId().toInt()).arg(userId().toInt());
}
- disconnect(&socket, 0, this, 0); // this keeps the socket from triggering events during clean up
- delete _userInputHandler;
}
}
// Request a socket-level disconnect if not already happened
socket.disconnectFromHost();
- // Return the result of waiting for disconnect; true if successful, otherwise false
- return socket.waitForDisconnected(msecs);
+ if (socket.state() != QAbstractSocket::UnconnectedState) {
+ return socket.waitForDisconnected(msecs);
+ }
+ return true;
}
void CoreNetwork::connectToIrc(bool reconnecting)
{
+ if (_shuttingDown) {
+ return;
+ }
+
+ if (Core::instance()->identServer()) {
+ _socketId = Core::instance()->identServer()->addWaitingSocket();
+ }
+
if (!reconnecting && useAutoReconnect() && _autoReconnectCount == 0) {
_autoReconnectTimer.setInterval(autoReconnectInterval() * 1000);
if (unlimitedReconnectRetries())
else if (_previousConnectionAttemptFailed) {
// cycle to next server if previous connection attempt failed
_previousConnectionAttemptFailed = false;
- displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Connection failed. Cycling to next Server"));
+ showMessage(Message::Server, BufferInfo::StatusBuffer, "", tr("Connection failed. Cycling to next Server"));
if (++_lastUsedServerIndex >= serverList().size()) {
_lastUsedServerIndex = 0;
}
Server server = usedServer();
displayStatusMsg(tr("Connecting to %1:%2...").arg(server.host).arg(server.port));
- displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Connecting to %1:%2...").arg(server.host).arg(server.port));
+ showMessage(Message::Server, BufferInfo::StatusBuffer, "", tr("Connecting to %1:%2...").arg(server.host).arg(server.port));
if (server.useProxy) {
QNetworkProxy proxy((QNetworkProxy::ProxyType)server.proxyType, server.proxyHost, server.proxyPort, server.proxyUser, server.proxyPass);
enablePingTimeout();
+ // Reset tracking for valid timestamps in PONG replies
+ setPongTimestampValid(false);
+
// Qt caches DNS entries for a minute, resulting in round-robin (e.g. for chat.freenode.net) not working if several users
// connect at a similar time. QHostInfo::fromName(), however, always performs a fresh lookup, overwriting the cache entry.
if (! server.useProxy) {
}
-void CoreNetwork::disconnectFromIrc(bool requested, const QString &reason, bool withReconnect,
- bool forceImmediate)
+void CoreNetwork::disconnectFromIrc(bool requested, const QString &reason, bool withReconnect)
{
// Disconnecting from the network, should expect a socket close or error
_disconnectExpected = true;
else
_quitReason = reason;
- displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Disconnecting. (%1)").arg((!requested && !withReconnect) ? tr("Core Shutdown") : _quitReason));
+ showMessage(Message::Server, BufferInfo::StatusBuffer, "", tr("Disconnecting. (%1)").arg((!requested && !withReconnect) ? tr("Core Shutdown") : _quitReason));
if (socket.state() == QAbstractSocket::UnconnectedState) {
- socketDisconnected();
- } else {
+ onSocketDisconnected();
+ }
+ else {
if (socket.state() == QAbstractSocket::ConnectedState) {
- userInputHandler()->issueQuit(_quitReason, forceImmediate);
- } else {
+ // If shutting down, prioritize the QUIT command
+ userInputHandler()->issueQuit(_quitReason, _shuttingDown);
+ }
+ else {
socket.close();
}
- if (requested || withReconnect) {
- // the irc server has 10 seconds to close the socket
+ if (socket.state() != QAbstractSocket::UnconnectedState) {
+ // Wait for up to 10 seconds for the socket to close cleanly, then it will be forcefully aborted
_socketCloseTimer.start(10000);
}
}
}
-void CoreNetwork::userInput(BufferInfo buf, QString msg)
+void CoreNetwork::onSocketCloseTimeout()
+{
+ qWarning() << QString{"Timed out quitting network %1 (network ID: %2, user ID: %3)"}
+ .arg(networkName()).arg(networkId().toInt()).arg(userId().toInt());
+ socket.abort();
+}
+
+
+void CoreNetwork::shutdown()
+{
+ _shuttingDown = true;
+ disconnectFromIrc(false, {}, false);
+}
+
+
+void CoreNetwork::userInput(const BufferInfo &buf, QString msg)
{
userInputHandler()->handleUserInput(buf, msg);
}
-void CoreNetwork::putRawLine(const QByteArray s, const bool prepend)
+void CoreNetwork::putRawLine(const QByteArray &s, bool prepend)
{
- if (_tokenBucket > 0 || (_skipMessageRates && _msgQueue.size() == 0)) {
+ if (_tokenBucket > 0 || (_skipMessageRates && _msgQueue.isEmpty())) {
// If there's tokens remaining, ...
// Or rate limits don't apply AND no messages are in queue (to prevent out-of-order), ...
// Send the message now.
Cipher *CoreNetwork::cipher(const QString &target)
{
if (target.isEmpty())
- return 0;
+ return nullptr;
if (!Cipher::neededFeaturesAvailable())
- return 0;
+ return nullptr;
- CoreIrcChannel *channel = qobject_cast<CoreIrcChannel *>(ircChannel(target));
+ auto *channel = qobject_cast<CoreIrcChannel *>(ircChannel(target));
if (channel) {
return channel->cipher();
}
- CoreIrcUser *user = qobject_cast<CoreIrcUser *>(ircUser(target));
+ auto *user = qobject_cast<CoreIrcUser *>(ircUser(target));
if (user) {
return user->cipher();
} else if (!isChannelName(target)) {
return qobject_cast<CoreIrcUser*>(newIrcUser(target))->cipher();
}
- return 0;
+ return nullptr;
}
QByteArray CoreNetwork::cipherKey(const QString &target) const
{
- CoreIrcChannel *c = qobject_cast<CoreIrcChannel*>(ircChannel(target));
+ auto *c = qobject_cast<CoreIrcChannel*>(ircChannel(target));
if (c)
return c->cipher()->key();
- CoreIrcUser *u = qobject_cast<CoreIrcUser*>(ircUser(target));
+ auto *u = qobject_cast<CoreIrcUser*>(ircUser(target));
if (u)
return u->cipher()->key();
void CoreNetwork::setCipherKey(const QString &target, const QByteArray &key)
{
- CoreIrcChannel *c = qobject_cast<CoreIrcChannel*>(ircChannel(target));
+ auto *c = qobject_cast<CoreIrcChannel*>(ircChannel(target));
if (c) {
c->setEncrypted(c->cipher()->setKey(key));
coreSession()->setBufferCipher(networkId(), target, key);
return;
}
- CoreIrcUser *u = qobject_cast<CoreIrcUser*>(ircUser(target));
+ auto *u = qobject_cast<CoreIrcUser*>(ircUser(target));
if (!u && !isChannelName(target))
u = qobject_cast<CoreIrcUser*>(newIrcUser(target));
bool CoreNetwork::cipherUsesCBC(const QString &target)
{
- CoreIrcChannel *c = qobject_cast<CoreIrcChannel*>(ircChannel(target));
+ auto *c = qobject_cast<CoreIrcChannel*>(ircChannel(target));
if (c)
return c->cipher()->usesCBC();
- CoreIrcUser *u = qobject_cast<CoreIrcUser*>(ircUser(target));
+ auto *u = qobject_cast<CoreIrcUser*>(ircUser(target));
if (u)
return u->cipher()->usesCBC();
}
#endif /* HAVE_QCA2 */
-bool CoreNetwork::setAutoWhoDone(const QString &channel)
+bool CoreNetwork::setAutoWhoDone(const QString &name)
{
- QString chan = channel.toLower();
- if (_autoWhoPending.value(chan, 0) <= 0)
+ QString chanOrNick = name.toLower();
+ if (_autoWhoPending.value(chanOrNick, 0) <= 0)
return false;
- if (--_autoWhoPending[chan] <= 0)
- _autoWhoPending.remove(chan);
+ if (--_autoWhoPending[chanOrNick] <= 0)
+ _autoWhoPending.remove(chanOrNick);
return true;
}
}
-void CoreNetwork::socketHasData()
+void CoreNetwork::onSocketHasData()
{
while (socket.canReadLine()) {
QByteArray s = socket.readLine();
}
-void CoreNetwork::socketError(QAbstractSocket::SocketError error)
+void CoreNetwork::onSocketError(QAbstractSocket::SocketError error)
{
// Ignore socket closed errors if expected
if (_disconnectExpected && error == QAbstractSocket::RemoteHostClosedError) {
_previousConnectionAttemptFailed = true;
qWarning() << qPrintable(tr("Could not connect to %1 (%2)").arg(networkName(), socket.errorString()));
emit connectionError(socket.errorString());
- displayMsg(Message::Error, BufferInfo::StatusBuffer, "", tr("Connection failure: %1").arg(socket.errorString()));
+ showMessage(Message::Error, BufferInfo::StatusBuffer, "", tr("Connection failure: %1").arg(socket.errorString()));
emitConnectionError(socket.errorString());
if (socket.state() < QAbstractSocket::ConnectedState) {
- socketDisconnected();
+ onSocketDisconnected();
}
}
-void CoreNetwork::socketInitialized()
+void CoreNetwork::onSocketInitialized()
{
CoreIdentity *identity = identityPtr();
if (!identity) {
// Non-SSL connections enter here only once, always emit socketInitialized(...) in these cases
// SSL connections call socketInitialized() twice, only emit socketInitialized(...) on the first (not yet encrypted) run
if (!server.useSsl || !socket.isEncrypted()) {
- emit socketInitialized(identity, localAddress(), localPort(), peerAddress(), peerPort());
+ emit socketInitialized(identity, localAddress(), localPort(), peerAddress(), peerPort(), _socketId);
}
if (server.useSsl && !socket.isEncrypted()) {
return;
}
#else
- emit socketInitialized(identity, localAddress(), localPort(), peerAddress(), peerPort());
+ emit socketInitialized(identity, localAddress(), localPort(), peerAddress(), peerPort(), _socketId);
#endif
socket.setSocketOption(QAbstractSocket::KeepAliveOption, true);
// Request capabilities as per IRCv3.2 specifications
// Older servers should ignore this; newer servers won't downgrade to RFC1459
- displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Requesting capability list..."));
+ showMessage(Message::Server, BufferInfo::StatusBuffer, "", tr("Requesting capability list..."));
putRawLine(serverEncode(QString("CAP LS 302")));
if (!server.password.isEmpty()) {
nick = identity->nicks()[0];
}
putRawLine(serverEncode(QString("NICK %1").arg(nick)));
- putRawLine(serverEncode(QString("USER %1 8 * :%2").arg(identity->ident(), identity->realName())));
+ // Only allow strict-compliant idents when strict mode is enabled
+ putRawLine(serverEncode(QString("USER %1 8 * :%2").arg(
+ coreSession()->strictCompliantIdent(identity),
+ identity->realName())));
}
-void CoreNetwork::socketDisconnected()
+void CoreNetwork::onSocketDisconnected()
{
disablePingTimeout();
_msgQueue.clear();
IrcUser *me_ = me();
if (me_) {
foreach(QString channel, me_->channels())
- displayMsg(Message::Quit, BufferInfo::ChannelBuffer, channel, _quitReason, me_->hostmask());
+ showMessage(Message::Quit, BufferInfo::ChannelBuffer, channel, _quitReason, me_->hostmask());
}
setConnected(false);
emit disconnected(networkId());
- emit socketDisconnected(identityPtr(), localAddress(), localPort(), peerAddress(), peerPort());
+ emit socketDisconnected(identityPtr(), localAddress(), localPort(), peerAddress(), peerPort(), _socketId);
// Reset disconnect expectations
_disconnectExpected = false;
if (_quitRequested) {
}
-void CoreNetwork::socketStateChanged(QAbstractSocket::SocketState socketState)
+void CoreNetwork::onSocketStateChanged(QAbstractSocket::SocketState socketState)
{
Network::ConnectionState state;
switch (socketState) {
case QAbstractSocket::UnconnectedState:
state = Network::Disconnected;
- socketDisconnected();
+ onSocketDisconnected();
break;
case QAbstractSocket::HostLookupState:
case QAbstractSocket::ConnectingState:
restoreUserModes();
}
else {
- connect(me_, SIGNAL(userModesSet(QString)), this, SLOT(restoreUserModes()));
- connect(me_, SIGNAL(userModesAdded(QString)), this, SLOT(restoreUserModes()));
+ connect(me_, &IrcUser::userModesSet, this, &CoreNetwork::restoreUserModes);
+ connect(me_, &IrcUser::userModesAdded, this, &CoreNetwork::restoreUserModes);
}
}
IrcUser *me_ = me();
Q_ASSERT(me_);
- disconnect(me_, SIGNAL(userModesSet(QString)), this, SLOT(restoreUserModes()));
- disconnect(me_, SIGNAL(userModesAdded(QString)), this, SLOT(restoreUserModes()));
+ disconnect(me_, &IrcUser::userModesSet, this, &CoreNetwork::restoreUserModes);
+ disconnect(me_, &IrcUser::userModesAdded, this, &CoreNetwork::restoreUserModes);
QString modesDelta = Core::userModes(userId(), networkId());
QString currentModes = me_->userModes();
QString removeModes;
bool addMode = true;
- for (int i = 0; i < requestedModes.length(); i++) {
- if (requestedModes[i] == '+') {
+ for (auto requestedMode : requestedModes) {
+ if (requestedMode == '+') {
addMode = true;
continue;
}
- if (requestedModes[i] == '-') {
+ if (requestedMode == '-') {
addMode = false;
continue;
}
if (addMode) {
- addModes += requestedModes[i];
+ addModes += requestedMode;
}
else {
- removeModes += requestedModes[i];
+ removeModes += requestedMode;
}
}
void CoreNetwork::sendPing()
{
- uint now = QDateTime::currentDateTime().toTime_t();
+ qint64 now = QDateTime::currentDateTime().toMSecsSinceEpoch();
if (_pingCount != 0) {
qDebug() << "UserId:" << userId() << "Network:" << networkName() << "missed" << _pingCount << "pings."
<< "BA:" << socket.bytesAvailable() << "BTW:" << socket.bytesToWrite();
}
- if ((int)_pingCount >= networkConfig()->maxPingCount() && now - _lastPingTime <= (uint)(_pingTimer.interval() / 1000) + 1) {
+ if ((int)_pingCount >= networkConfig()->maxPingCount()
+ && (now - _lastPingTime) <= (_pingTimer.interval() + (1 * 1000))) {
+ // In transitioning to 64-bit time, the interval no longer needs converted down to seconds.
+ // However, to reduce the risk of breaking things by changing past behavior, we still allow
+ // up to 1 second missed instead of enforcing a stricter 1 millisecond allowance.
+ //
// the second check compares the actual elapsed time since the last ping and the pingTimer interval
// if the interval is shorter then the actual elapsed time it means that this thread was somehow blocked
// and unable to even handle a ping answer. So we ignore those misses.
_lastPingTime = now;
_pingCount++;
// Don't send pings until the network is initialized
- if(_sendPings)
+ if(_sendPings) {
+ // Mark as waiting for a reply
+ _pongReplyPending = true;
+ // Send default timestamp ping
userInputHandler()->handlePing(BufferInfo(), QString());
+ }
}
}
disablePingTimeout();
else {
resetPingTimeout();
+ resetPongReplyPending();
if (networkConfig()->pingTimeoutEnabled())
_pingTimer.start();
}
_pingTimer.stop();
_sendPings = false;
resetPingTimeout();
+ resetPongReplyPending();
}
}
+void CoreNetwork::setPongTimestampValid(bool validTimestamp)
+{
+ _pongTimestampValid = validTimestamp;
+}
+
+
/******** Custom Rate Limiting ********/
void CoreNetwork::updateRateLimiting(const bool forceUnlimited)
if (_skipMessageRates) {
// If the message queue already contains messages, they need sent before disabling the
// timer. Set the timer to a rapid pace and let it disable itself.
- if (_msgQueue.size() > 0) {
+ if (!_msgQueue.isEmpty()) {
qDebug() << "Outgoing message queue contains messages while disabling rate "
"limiting. Sending remaining queued messages...";
// Promptly run the timer again to clear the messages. Rate limiting is disabled,
// EXTERNAL authentication supported, send request
putRawLine(serverEncode("AUTHENTICATE EXTERNAL"));
} else {
- displayMsg(Message::Error, BufferInfo::StatusBuffer, "",
- tr("SASL EXTERNAL authentication not supported"));
+ showMessage(Message::Error, BufferInfo::StatusBuffer, "", tr("SASL EXTERNAL authentication not supported"));
sendNextCap();
}
} else {
// Only working with PLAIN atm, blowfish later
putRawLine(serverEncode("AUTHENTICATE PLAIN"));
} else {
- displayMsg(Message::Error, BufferInfo::StatusBuffer, "",
- tr("SASL PLAIN authentication not supported"));
+ showMessage(Message::Error, BufferInfo::StatusBuffer, "", tr("SASL PLAIN authentication not supported"));
sendNextCap();
}
#ifdef HAVE_SSL
// Add most recently tried capability set to individual list, re-requesting them one at a time
_capsQueuedIndividual.append(_capsQueuedLastBundle);
// Warn of this issue to explain the slower login. Servers usually shouldn't trigger this.
- displayMsg(Message::Server, BufferInfo::StatusBuffer, "",
- tr("Could not negotiate some capabilities, retrying individually (%1)...")
- .arg(_capsQueuedLastBundle.join(", ")));
+ showMessage(Message::Server, BufferInfo::StatusBuffer, "",
+ tr("Could not negotiate some capabilities, retrying individually (%1)...")
+ .arg(_capsQueuedLastBundle.join(", ")));
// Capabilities are already removed from the capability bundle queue via takeQueuedCaps(), no
// need to remove them here.
// Clear the most recently tried set to reduce risk that mistakes elsewhere causes retrying
if (!capNegotiationInProgress()) {
// If the server doesn't have any capabilities, but supports CAP LS, continue on with the
// normal connection.
- displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("No capabilities available"));
+ showMessage(Message::Server, BufferInfo::StatusBuffer, "", tr("No capabilities available"));
endCapNegotiation();
return;
}
_capNegotiationActive = true;
- displayMsg(Message::Server, BufferInfo::StatusBuffer, "",
- tr("Ready to negotiate (found: %1)").arg(caps().join(", ")));
+ showMessage(Message::Server, BufferInfo::StatusBuffer, "", tr("Ready to negotiate (found: %1)").arg(caps().join(", ")));
// Build a list of queued capabilities, starting with individual, then bundled, only adding the
// comma separator between the two if needed (both individual and bundled caps exist).
_capsQueuedIndividual.join(", ")
+ ((!_capsQueuedIndividual.empty() && !_capsQueuedBundled.empty()) ? ", " : "")
+ _capsQueuedBundled.join(", ");
- displayMsg(Message::Server, BufferInfo::StatusBuffer, "",
- tr("Negotiating capabilities (requesting: %1)...").arg(queuedCapsDisplay));
+ showMessage(Message::Server, BufferInfo::StatusBuffer, "", tr("Negotiating capabilities (requesting: %1)...").arg(queuedCapsDisplay));
sendNextCap();
}
// No pending desired capabilities, capability negotiation finished
// If SASL requested but not available, print a warning
if (networkInfo().useSasl && !capEnabled(IrcCap::SASL))
- displayMsg(Message::Error, BufferInfo::StatusBuffer, "",
- tr("SASL authentication currently not supported by server"));
+ showMessage(Message::Error, BufferInfo::StatusBuffer, "", tr("SASL authentication currently not supported by server"));
if (_capNegotiationActive) {
- displayMsg(Message::Server, BufferInfo::StatusBuffer, "",
- tr("Capability negotiation finished (enabled: %1)").arg(capsEnabled().join(", ")));
+ showMessage(Message::Server, BufferInfo::StatusBuffer, "", tr("Capability negotiation finished (enabled: %1)").arg(capsEnabled().join(", ")));
_capNegotiationActive = false;
}
_autoWhoQueue = channels();
}
-void CoreNetwork::queueAutoWhoOneshot(const QString &channelOrNick)
+void CoreNetwork::queueAutoWhoOneshot(const QString &name)
{
// Prepend so these new channels/nicks are the first to be checked
// Don't allow duplicates
- if (!_autoWhoQueue.contains(channelOrNick.toLower())) {
- _autoWhoQueue.prepend(channelOrNick.toLower());
+ if (!_autoWhoQueue.contains(name.toLower())) {
+ _autoWhoQueue.prepend(name.toLower());
}
if (capEnabled(IrcCap::AWAY_NOTIFY)) {
// When away-notify is active, the timer's stopped. Start a new cycle to who this channel.
}
if (supports("WHOX")) {
// Use WHO extended to poll away users and/or user accounts
+ // Explicitly only match on nickname ("n"), don't rely on server defaults
+ //
+ // WHO <nickname> n%chtsunfra,<unique_number>
+ //
// See http://faerion.sourceforge.net/doc/irc/whox.var
- // And https://github.com/hexchat/hexchat/blob/c874a9525c9b66f1d5ddcf6c4107d046eba7e2c5/src/common/proto-irc.c#L750
- putRawLine(serverEncode(QString("WHO %1 %%chtsunfra,%2")
- .arg(serverEncode(chanOrNick), QString::number(IrcCap::ACCOUNT_NOTIFY_WHOX_NUM))));
+ // And https://github.com/quakenet/snircd/blob/master/doc/readme.who
+ // And https://github.com/hexchat/hexchat/blob/57478b65758e6b697b1d82ce21075e74aa475efc/src/common/proto-irc.c#L752
+ putRawLine(serverEncode(QString("WHO %1 n%chtsunfra,%2")
+ .arg(serverEncode(chanOrNick),
+ QString::number(IrcCap::ACCOUNT_NOTIFY_WHOX_NUM))));
} else {
+ // Fall back to normal WHO
+ //
+ // Note: According to RFC 1459, "WHO <phrase>" can fall back to searching realname,
+ // hostmask, etc. There's nothing we can do about that :(
+ //
+ // See https://tools.ietf.org/html/rfc1459#section-4.5.1
putRawLine(serverEncode(QString("WHO %1").arg(chanOrNick)));
}
break;
#ifdef HAVE_SSL
-void CoreNetwork::sslErrors(const QList<QSslError> &sslErrors)
+void CoreNetwork::onSslErrors(const QList<QSslError> &sslErrors)
{
Server server = usedServer();
if (server.sslVerify) {
// Add the error reason if known
sslErrorMessage.append(tr(" (Reason: %1)").arg(sslErrors.first().errorString()));
}
- displayMsg(Message::Error, BufferInfo::StatusBuffer, "", sslErrorMessage);
+ showMessage(Message::Error, BufferInfo::StatusBuffer, "", sslErrorMessage);
// Disconnect, triggering a reconnect in case it's a temporary issue with certificate
// validity, network trouble, etc.
// Add the error reason if known
sslErrorMessage.append(tr(" (Reason: %1)").arg(sslErrors.first().errorString()));
}
- displayMsg(Message::Info, BufferInfo::StatusBuffer, "", sslErrorMessage);
+ showMessage(Message::Info, BufferInfo::StatusBuffer, "", sslErrorMessage);
// Proceed with the connection
socket.ignoreSslErrors();
void CoreNetwork::writeToSocket(const QByteArray &data)
{
+ // Log the message if enabled and network ID matches or allows all
+ if (_debugLogRawIrc
+ && (_debugLogRawNetId == -1 || networkId().toInt() == _debugLogRawNetId)) {
+ // Include network ID
+ qDebug() << "IRC net" << networkId() << ">>" << data;
+ }
socket.write(data);
socket.write("\r\n");
if (!_skipMessageRates) {
void CoreNetwork::requestConnect() const
{
+ if (_shuttingDown) {
+ return;
+ }
if (connectionState() != Disconnected) {
qWarning() << "Requesting connect while already being connected!";
return;
void CoreNetwork::requestDisconnect() const
{
+ if (_shuttingDown) {
+ return;
+ }
if (connectionState() == Disconnected) {
qWarning() << "Requesting disconnect while not being connected!";
return;
}
-QList<QList<QByteArray>> CoreNetwork::splitMessage(const QString &cmd, const QString &message, std::function<QList<QByteArray>(QString &)> cmdGenerator)
+QList<QList<QByteArray>> CoreNetwork::splitMessage(const QString &cmd, const QString &message, const std::function<QList<QByteArray>(QString &)> &cmdGenerator)
{
QString wrkMsg(message);
QList<QList<QByteArray>> msgsToSend;