X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=blobdiff_plain;f=src%2Fcore%2Fcorenetwork.cpp;h=05da4135fd6c7de138910a5607ac862f16275e22;hp=b9e82e44a4710e2147d55bc36c7ac710d000a930;hb=86837eac3346857733f3488acccca9dc2c45605e;hpb=841df8c7cc71ff1b8099d49296c8e997c92bdd60 diff --git a/src/core/corenetwork.cpp b/src/core/corenetwork.cpp index b9e82e44..05da4135 100644 --- a/src/core/corenetwork.cpp +++ b/src/core/corenetwork.cpp @@ -20,19 +20,21 @@ #include "corenetwork.h" +#include "ctcphandler.h" #include "core.h" -#include "coresession.h" #include "coreidentity.h" - +#include "corenetworkconfig.h" +#include "coresession.h" +#include "coreuserinputhandler.h" #include "ircserverhandler.h" -#include "userinputhandler.h" -#include "ctcphandler.h" +#include "networkevent.h" +INIT_SYNCABLE_OBJECT(CoreNetwork) CoreNetwork::CoreNetwork(const NetworkId &networkid, CoreSession *session) : Network(networkid, session), _coreSession(session), _ircServerHandler(new IrcServerHandler(this)), - _userInputHandler(new UserInputHandler(this)), + _userInputHandler(new CoreUserInputHandler(this)), _ctcpHandler(new CtcpHandler(this)), _autoReconnectCount(0), _quitRequested(false), @@ -40,33 +42,35 @@ CoreNetwork::CoreNetwork(const NetworkId &networkid, CoreSession *session) _previousConnectionAttemptFailed(false), _lastUsedServerIndex(0), - // TODO make autowho configurable (possibly per-network) - _autoWhoEnabled(true), - _autoWhoInterval(90), - _autoWhoNickLimit(0), // unlimited - _autoWhoDelay(3) + _lastPingTime(0), + _pingCount(0), + _requestedUserModes('-') { _autoReconnectTimer.setSingleShot(true); _socketCloseTimer.setSingleShot(true); connect(&_socketCloseTimer, SIGNAL(timeout()), this, SLOT(socketCloseTimeout())); - _pingTimer.setInterval(60000); + setPingInterval(networkConfig()->pingInterval()); connect(&_pingTimer, SIGNAL(timeout()), this, SLOT(sendPing())); - _autoWhoTimer.setInterval(_autoWhoDelay * 1000); - _autoWhoCycleTimer.setInterval(_autoWhoInterval * 1000); + setAutoWhoDelay(networkConfig()->autoWhoDelay()); + setAutoWhoInterval(networkConfig()->autoWhoInterval()); QHash channels = coreSession()->persistentChannels(networkId()); foreach(QString chan, channels.keys()) { _channelKeys[chan.toLower()] = channels[chan]; } + 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(&_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(fillBucketAndProcessQueue())); - connect(this, SIGNAL(connectRequested()), this, SLOT(connectToIrc())); - connect(&socket, SIGNAL(connected()), this, SLOT(socketInitialized())); connect(&socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected())); @@ -181,10 +185,14 @@ void CoreNetwork::connectToIrc(bool reconnecting) { #endif } -void CoreNetwork::disconnectFromIrc(bool requested, const QString &reason) { +void CoreNetwork::disconnectFromIrc(bool requested, const QString &reason, bool withReconnect) { _quitRequested = requested; // see socketDisconnected(); - _autoReconnectTimer.stop(); - _autoReconnectCount = 0; // prohibiting auto reconnect + if(!withReconnect) { + _autoReconnectTimer.stop(); + _autoReconnectCount = 0; // prohibiting auto reconnect + } + disablePingTimeout(); + _msgQueue.clear(); IrcUser *me_ = me(); if(me_) { @@ -192,27 +200,25 @@ void CoreNetwork::disconnectFromIrc(bool requested, const QString &reason) { if(me_->isAway()) awayMsg = me_->awayMessage(); Core::setAwayMessage(userId(), networkId(), awayMsg); - Core::setUserModes(userId(), networkId(), me_->userModes()); } - displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Disconnecting.")); - if(socket.state() == QAbstractSocket::UnconnectedState) { - socketDisconnected(); - } else if(socket.state() < QAbstractSocket::ConnectedState || !requested) { - // we might be in a state waiting for a timeout... - // or (!requested) this is a core shutdown... - // in both cases we don't really care... set a disconnected state + if(reason.isEmpty() && identityPtr()) + _quitReason = identityPtr()->quitReason(); + else + _quitReason = reason; + + displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Disconnecting. (%1)").arg((!requested && !withReconnect) ? tr("Core Shutdown") : _quitReason)); + switch(socket.state()) { + case QAbstractSocket::ConnectedState: + userInputHandler()->issueQuit(_quitReason); + if(requested || withReconnect) { + // the irc server has 10 seconds to close the socket + _socketCloseTimer.start(10000); + break; + } + default: socket.close(); socketDisconnected(); - } else { - // quit gracefully if it's user requested quit - if(reason.isEmpty() && identityPtr()) - _quitReason = identityPtr()->quitReason(); - else - _quitReason = reason; - userInputHandler()->issueQuit(_quitReason); - // the irc server has 10 seconds to close the socket - _socketCloseTimer.start(10000); } } @@ -253,7 +259,7 @@ void CoreNetwork::setChannelJoined(const QString &channel) { void CoreNetwork::setChannelParted(const QString &channel) { removeChannelKey(channel); _autoWhoQueue.removeAll(channel.toLower()); - _autoWhoInProgress.remove(channel.toLower()); + _autoWhoPending.remove(channel.toLower()); Core::setChannelPersistent(userId(), networkId(), channel, false); } @@ -270,10 +276,45 @@ void CoreNetwork::removeChannelKey(const QString &channel) { _channelKeys.remove(channel.toLower()); } +#ifdef HAVE_QCA2 +Cipher *CoreNetwork::cipher(const QString &target) const { + if(target.isEmpty()) + return 0; + + QByteArray key = cipherKey(target); + if(key.isEmpty()) + return 0; + + CoreIrcChannel *channel = qobject_cast(ircChannel(target)); + if(channel) { + if(channel->cipher()->setKey(key)) + return channel->cipher(); + } else { + CoreIrcUser *user = qobject_cast(ircUser(target)); + if(user && user->cipher()->setKey(key)) + return user->cipher(); + } + return 0; +} + +QByteArray CoreNetwork::cipherKey(const QString &recipient) const { + return _cipherKeys.value(recipient.toLower(), QByteArray()); +} + +void CoreNetwork::setCipherKey(const QString &recipient, const QByteArray &key) { + if(!key.isEmpty()) + _cipherKeys[recipient.toLower()] = key; + else + _cipherKeys.remove(recipient.toLower()); +} +#endif /* HAVE_QCA2 */ + bool CoreNetwork::setAutoWhoDone(const QString &channel) { - if(_autoWhoInProgress.value(channel.toLower(), 0) <= 0) + QString chan = channel.toLower(); + if(_autoWhoPending.value(chan, 0) <= 0) return false; - _autoWhoInProgress[channel.toLower()]--; + if(--_autoWhoPending[chan] <= 0) + _autoWhoPending.remove(chan); return true; } @@ -286,7 +327,9 @@ void CoreNetwork::setMyNick(const QString &mynick) { void CoreNetwork::socketHasData() { while(socket.canReadLine()) { QByteArray s = socket.readLine().trimmed(); - ircServerHandler()->handleServerMsg(s); + ircServerHandler()->handleServerMsg(s); // FIXME remove with events + + coreSession()->eventManager()->sendEvent(new NetworkDataEvent(EventManager::NetworkIncoming, this, s)); } } @@ -297,7 +340,7 @@ void CoreNetwork::socketError(QAbstractSocket::SocketError error) { _previousConnectionAttemptFailed = true; qWarning() << qPrintable(tr("Could not connect to %1 (%2)").arg(networkName(), socket.errorString())); emit connectionError(socket.errorString()); - emit displayMsg(Message::Error, BufferInfo::StatusBuffer, "", tr("Connection failure: %1").arg(socket.errorString())); + displayMsg(Message::Error, BufferInfo::StatusBuffer, "", tr("Connection failure: %1").arg(socket.errorString())); emitConnectionError(socket.errorString()); if(socket.state() < QAbstractSocket::ConnectedState) { socketDisconnected(); @@ -319,24 +362,36 @@ void CoreNetwork::socketInitialized() { } // TokenBucket to avoid sending too much at once - _messagesPerSecond = 1; + _messageDelay = 2200; // this seems to be a safe value (2.2 seconds delay) _burstSize = 5; - _tokenBucket = 5; // init with a full bucket - _tokenBucketTimer.start(_messagesPerSecond * 1000); + _tokenBucket = _burstSize; // init with a full bucket + _tokenBucketTimer.start(_messageDelay); + if(networkInfo().useSasl) { + putRawLine(serverEncode(QString("CAP REQ :sasl"))); + } if(!server.password.isEmpty()) { putRawLine(serverEncode(QString("PASS %1").arg(server.password))); } - putRawLine(serverEncode(QString("NICK :%1").arg(identity->nicks()[0]))); + QString nick; + if(identity->nicks().isEmpty()) { + nick = "quassel"; + qWarning() << "CoreNetwork::socketInitialized(): no nicks supplied for identity Id" << identity->id(); + } else { + nick = identity->nicks()[0]; + } + putRawLine(serverEncode(QString("NICK :%1").arg(nick))); putRawLine(serverEncode(QString("USER %1 8 * :%2").arg(identity->ident(), identity->realName()))); } void CoreNetwork::socketDisconnected() { - _pingTimer.stop(); + disablePingTimeout(); + _msgQueue.clear(); + _autoWhoCycleTimer.stop(); _autoWhoTimer.stop(); _autoWhoQueue.clear(); - _autoWhoInProgress.clear(); + _autoWhoPending.clear(); _socketCloseTimer.stop(); @@ -345,17 +400,18 @@ void CoreNetwork::socketDisconnected() { IrcUser *me_ = me(); if(me_) { foreach(QString channel, me_->channels()) - emit displayMsg(Message::Quit, BufferInfo::ChannelBuffer, channel, _quitReason, me_->hostmask()); + displayMsg(Message::Quit, BufferInfo::ChannelBuffer, channel, _quitReason, me_->hostmask()); } setConnected(false); emit disconnected(networkId()); if(_quitRequested) { + _quitRequested = false; setConnectionState(Network::Disconnected); Core::setNetworkConnected(userId(), networkId(), false); } else if(_autoReconnectCount != 0) { setConnectionState(Network::Reconnecting); - if(_autoReconnectCount == autoReconnectRetries()) + if(_autoReconnectCount == -1 || _autoReconnectCount == autoReconnectRetries()) doAutoReconnect(); // first try is immediate else _autoReconnectTimer.start(); @@ -391,28 +447,19 @@ void CoreNetwork::networkInitialized() { if(useAutoReconnect()) { // reset counter - _autoReconnectCount = autoReconnectRetries(); + _autoReconnectCount = unlimitedReconnectRetries() ? -1 : autoReconnectRetries(); } // restore away state - userInputHandler()->handleAway(BufferInfo(), Core::awayMessage(userId(), networkId())); - - // restore old user modes if server default mode is set. - IrcUser *me_ = me(); - if(me_) { - if(!me_->userModes().isEmpty()) { - restoreUserModes(); - } else { - connect(me_, SIGNAL(userModesSet(QString)), this, SLOT(restoreUserModes())); - connect(me_, SIGNAL(userModesAdded(QString)), this, SLOT(restoreUserModes())); - } - } + QString awayMsg = Core::awayMessage(userId(), networkId()); + if(!awayMsg.isEmpty()) + userInputHandler()->handleAway(BufferInfo(), Core::awayMessage(userId(), networkId())); sendPerform(); - _pingTimer.start(); + enablePingTimeout(); - if(_autoWhoEnabled) { + if(networkConfig()->autoWhoEnabled()) { _autoWhoCycleTimer.start(); _autoWhoTimer.start(); startAutoWhoCycle(); // FIXME wait for autojoin to be completed @@ -430,6 +477,17 @@ void CoreNetwork::sendPerform() { userInputHandler()->handleMsg(statusBuf, QString("%1 IDENTIFY %2").arg(autoIdentifyService(), autoIdentifyPassword())); } + // restore old user modes if server default mode is set. + IrcUser *me_ = me(); + if(me_) { + if(!me_->userModes().isEmpty()) { + restoreUserModes(); + } else { + connect(me_, SIGNAL(userModesSet(QString)), this, SLOT(restoreUserModes())); + connect(me_, SIGNAL(userModesAdded(QString)), this, SLOT(restoreUserModes())); + } + } + // send perform list foreach(QString line, perform()) { if(!line.isEmpty()) userInput(statusBuf, line); @@ -460,18 +518,118 @@ void CoreNetwork::restoreUserModes() { disconnect(me_, SIGNAL(userModesSet(QString)), this, SLOT(restoreUserModes())); disconnect(me_, SIGNAL(userModesAdded(QString)), this, SLOT(restoreUserModes())); - QString removeModes; - QString addModes = Core::userModes(userId(), networkId()); + QString modesDelta = Core::userModes(userId(), networkId()); QString currentModes = me_->userModes(); - removeModes = currentModes; - removeModes.remove(QRegExp(QString("[%1]").arg(addModes))); + QString addModes, removeModes; + if(modesDelta.contains('-')) { + addModes = modesDelta.section('-', 0, 0); + removeModes = modesDelta.section('-', 1); + } else { + addModes = modesDelta; + } + + addModes.remove(QRegExp(QString("[%1]").arg(currentModes))); + if(currentModes.isEmpty()) + removeModes = QString(); + else + removeModes.remove(QRegExp(QString("[^%1]").arg(currentModes))); + + if(addModes.isEmpty() && removeModes.isEmpty()) + return; + + if(!addModes.isEmpty()) + addModes = '+' + addModes; + if(!removeModes.isEmpty()) + removeModes = '-' + removeModes; - removeModes = QString("%1 -%2").arg(me_->nick(), removeModes); - addModes = QString("%1 +%2").arg(me_->nick(), addModes); - userInputHandler()->handleMode(BufferInfo(), removeModes); - userInputHandler()->handleMode(BufferInfo(), addModes); + // don't use InputHandler::handleMode() as it keeps track of our persistent mode changes + putRawLine(serverEncode(QString("MODE %1 %2%3").arg(me_->nick()).arg(addModes).arg(removeModes))); +} + +void CoreNetwork::updateIssuedModes(const QString &requestedModes) { + QString addModes; + QString removeModes; + bool addMode = true; + + for(int i = 0; i < requestedModes.length(); i++) { + if(requestedModes[i] == '+') { + addMode = true; + continue; + } + if(requestedModes[i] == '-') { + addMode = false; + continue; + } + if(addMode) { + addModes += requestedModes[i]; + } else { + removeModes += requestedModes[i]; + } + } + + + QString addModesOld = _requestedUserModes.section('-', 0, 0); + QString removeModesOld = _requestedUserModes.section('-', 1); + + addModes.remove(QRegExp(QString("[%1]").arg(addModesOld))); // deduplicate + addModesOld.remove(QRegExp(QString("[%1]").arg(removeModes))); // update + addModes += addModesOld; + + removeModes.remove(QRegExp(QString("[%1]").arg(removeModesOld))); // deduplicate + removeModesOld.remove(QRegExp(QString("[%1]").arg(addModes))); // update + removeModes += removeModesOld; + + _requestedUserModes = QString("%1-%2").arg(addModes).arg(removeModes); +} + +void CoreNetwork::updatePersistentModes(QString addModes, QString removeModes) { + QString persistentUserModes = Core::userModes(userId(), networkId()); + + QString requestedAdd = _requestedUserModes.section('-', 0, 0); + QString requestedRemove = _requestedUserModes.section('-', 1); + + QString persistentAdd, persistentRemove; + if(persistentUserModes.contains('-')) { + persistentAdd = persistentUserModes.section('-', 0, 0); + persistentRemove = persistentUserModes.section('-', 1); + } else { + persistentAdd = persistentUserModes; + } + + // remove modes we didn't issue + if(requestedAdd.isEmpty()) + addModes = QString(); + else + addModes.remove(QRegExp(QString("[^%1]").arg(requestedAdd))); + + if(requestedRemove.isEmpty()) + removeModes = QString(); + else + removeModes.remove(QRegExp(QString("[^%1]").arg(requestedRemove))); + + // deduplicate + persistentAdd.remove(QRegExp(QString("[%1]").arg(addModes))); + persistentRemove.remove(QRegExp(QString("[%1]").arg(removeModes))); + + // update + persistentAdd.remove(QRegExp(QString("[%1]").arg(removeModes))); + persistentRemove.remove(QRegExp(QString("[%1]").arg(addModes))); + + // update issued mode list + requestedAdd.remove(QRegExp(QString("[%1]").arg(addModes))); + requestedRemove.remove(QRegExp(QString("[%1]").arg(removeModes))); + _requestedUserModes = QString("%1-%2").arg(requestedAdd).arg(requestedRemove); + + persistentAdd += addModes; + persistentRemove += removeModes; + Core::setUserModes(userId(), networkId(), QString("%1-%2").arg(persistentAdd).arg(persistentRemove)); +} + +void CoreNetwork::resetPersistentModes() { + _requestedUserModes = QString('-'); + Core::setUserModes(userId(), networkId(), QString()); } void CoreNetwork::setUseAutoReconnect(bool use) { @@ -500,32 +658,50 @@ void CoreNetwork::doAutoReconnect() { qWarning() << "CoreNetwork::doAutoReconnect(): Cannot reconnect while not being disconnected!"; return; } - if(_autoReconnectCount > 0) - _autoReconnectCount--; + if(_autoReconnectCount > 0 || _autoReconnectCount == -1) + _autoReconnectCount--; // -2 means we delay the next reconnect connectToIrc(true); } void CoreNetwork::sendPing() { - userInputHandler()->handlePing(BufferInfo(), QString()); + uint now = QDateTime::currentDateTime().toTime_t(); + 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) { + // 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. + disconnectFromIrc(false, QString("No Ping reply in %1 seconds.").arg(_pingCount * _pingTimer.interval() / 1000), true /* withReconnect */); + } else { + _lastPingTime = now; + _pingCount++; + userInputHandler()->handlePing(BufferInfo(), QString()); + } } -void CoreNetwork::sendAutoWho() { - while(!_autoWhoQueue.isEmpty()) { - QString chan = _autoWhoQueue.takeFirst(); - IrcChannel *ircchan = ircChannel(chan); - if(!ircchan) continue; - if(_autoWhoNickLimit > 0 && ircchan->ircUsers().count() > _autoWhoNickLimit) continue; - _autoWhoInProgress[chan]++; - putRawLine("WHO " + serverEncode(chan)); - if(_autoWhoQueue.isEmpty() && _autoWhoEnabled && !_autoWhoCycleTimer.isActive()) { - // Timer was stopped, means a new cycle is due immediately - _autoWhoCycleTimer.start(); - startAutoWhoCycle(); - } - break; +void CoreNetwork::enablePingTimeout(bool enable) { + if(!enable) + disablePingTimeout(); + else { + resetPingTimeout(); + if(networkConfig()->pingTimeoutEnabled()) + _pingTimer.start(); } } +void CoreNetwork::disablePingTimeout() { + _pingTimer.stop(); + resetPingTimeout(); +} + +void CoreNetwork::setPingInterval(int interval) { + _pingTimer.setInterval(interval * 1000); +} + +/******** AutoWHO ********/ + void CoreNetwork::startAutoWhoCycle() { if(!_autoWhoQueue.isEmpty()) { _autoWhoCycleTimer.stop(); @@ -534,6 +710,45 @@ void CoreNetwork::startAutoWhoCycle() { _autoWhoQueue = channels(); } +void CoreNetwork::setAutoWhoDelay(int delay) { + _autoWhoTimer.setInterval(delay * 1000); +} + +void CoreNetwork::setAutoWhoInterval(int interval) { + _autoWhoCycleTimer.setInterval(interval * 1000); +} + +void CoreNetwork::setAutoWhoEnabled(bool enabled) { + if(enabled && isConnected() && !_autoWhoTimer.isActive()) + _autoWhoTimer.start(); + else if(!enabled) { + _autoWhoTimer.stop(); + _autoWhoCycleTimer.stop(); + } +} + +void CoreNetwork::sendAutoWho() { + // Don't send autowho if there are still some pending + if(_autoWhoPending.count()) + return; + + while(!_autoWhoQueue.isEmpty()) { + QString chan = _autoWhoQueue.takeFirst(); + IrcChannel *ircchan = ircChannel(chan); + if(!ircchan) continue; + if(networkConfig()->autoWhoNickLimit() > 0 && ircchan->ircUsers().count() >= networkConfig()->autoWhoNickLimit()) + continue; + _autoWhoPending[chan]++; + putRawLine("WHO " + serverEncode(chan)); + break; + } + if(_autoWhoQueue.isEmpty() && networkConfig()->autoWhoEnabled() && !_autoWhoCycleTimer.isActive()) { + // Timer was stopped, means a new cycle is due immediately + _autoWhoCycleTimer.start(); + startAutoWhoCycle(); + } +} + #ifdef HAVE_SSL void CoreNetwork::sslErrors(const QList &sslErrors) { Q_UNUSED(sslErrors) @@ -573,7 +788,7 @@ void CoreNetwork::requestConnect() const { qWarning() << "Requesting connect while already being connected!"; return; } - Network::requestConnect(); + QMetaObject::invokeMethod(const_cast(this), "connectToIrc", Qt::QueuedConnection); } void CoreNetwork::requestDisconnect() const {