-#include "ctcphandler.h"
-
-INIT_SYNCABLE_OBJECT(CoreNetwork)
-CoreNetwork::CoreNetwork(const NetworkId &networkid, CoreSession *session)
- : Network(networkid, session),
- _coreSession(session),
- _ircServerHandler(new IrcServerHandler(this)),
- _userInputHandler(new CoreUserInputHandler(this)),
- _ctcpHandler(new CtcpHandler(this)),
- _autoReconnectCount(0),
- _quitRequested(false),
-
- _previousConnectionAttemptFailed(false),
- _lastUsedServerIndex(0),
-
- _lastPingTime(0),
- _pingCount(0),
- _requestedUserModes('-')
-{
- _autoReconnectTimer.setSingleShot(true);
- _socketCloseTimer.setSingleShot(true);
- connect(&_socketCloseTimer, SIGNAL(timeout()), this, SLOT(socketCloseTimeout()));
-
- setPingInterval(networkConfig()->pingInterval());
- connect(&_pingTimer, SIGNAL(timeout()), this, SLOT(sendPing()));
-
- setAutoWhoDelay(networkConfig()->autoWhoDelay());
- setAutoWhoInterval(networkConfig()->autoWhoInterval());
-
- QHash<QString, QString> 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(&socket, SIGNAL(connected()), this, SLOT(socketInitialized()));
- connect(&socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected()));
- 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()));
-#ifdef HAVE_SSL
- connect(&socket, SIGNAL(encrypted()), this, SLOT(socketInitialized()));
- connect(&socket, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(sslErrors(const QList<QSslError> &)));
-#endif
-}
-
-CoreNetwork::~CoreNetwork() {
- if(connectionState() != Disconnected && connectionState() != Network::Reconnecting)
- disconnectFromIrc(false); // clean up, but this does not count as requested disconnect!
- disconnect(&socket, 0, this, 0); // this keeps the socket from triggering events during clean up
- delete _ircServerHandler;
- delete _userInputHandler;
- delete _ctcpHandler;
-}
-
-QString CoreNetwork::channelDecode(const QString &bufferName, const QByteArray &string) const {
- if(!bufferName.isEmpty()) {
- IrcChannel *channel = ircChannel(bufferName);
- if(channel)
- return channel->decodeString(string);
- }
- return decodeString(string);
-}
-
-QString CoreNetwork::userDecode(const QString &userNick, const QByteArray &string) const {
- IrcUser *user = ircUser(userNick);
- if(user)
- return user->decodeString(string);
- return decodeString(string);
-}
-
-QByteArray CoreNetwork::channelEncode(const QString &bufferName, const QString &string) const {
- if(!bufferName.isEmpty()) {
- IrcChannel *channel = ircChannel(bufferName);
- if(channel)
- return channel->encodeString(string);
- }
- return encodeString(string);
-}
-
-QByteArray CoreNetwork::userEncode(const QString &userNick, const QString &string) const {
- IrcUser *user = ircUser(userNick);
- if(user)
- return user->encodeString(string);
- return encodeString(string);
-}
-
-void CoreNetwork::connectToIrc(bool reconnecting) {
- if(!reconnecting && useAutoReconnect() && _autoReconnectCount == 0) {
- _autoReconnectTimer.setInterval(autoReconnectInterval() * 1000);
- if(unlimitedReconnectRetries())
- _autoReconnectCount = -1;
+#include "ircencoder.h"
+#include "irccap.h"
+#include "irctag.h"
+#include "networkevent.h"
+
+CoreNetwork::CoreNetwork(const NetworkId& networkid, CoreSession* session)
+ : Network(networkid, session)
+ , _coreSession(session)
+ , _userInputHandler(new CoreUserInputHandler(this))
+ , _metricsServer(Core::instance()->metricsServer())
+ , _autoReconnectCount(0)
+ , _quitRequested(false)
+ , _disconnectExpected(false)
+ , _previousConnectionAttemptFailed(false)
+ , _lastUsedServerIndex(0)
+ , _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, &QTimer::timeout, this, &CoreNetwork::onSocketCloseTimeout);
+
+ setPingInterval(networkConfig()->pingInterval());
+ connect(&_pingTimer, &QTimer::timeout, this, &CoreNetwork::sendPing);
+
+ setAutoWhoDelay(networkConfig()->autoWhoDelay());
+ setAutoWhoInterval(networkConfig()->autoWhoInterval());
+
+ QHash<QString, QString> channels = coreSession()->persistentChannels(networkId());
+ for (const QString& chan : channels.keys()) {
+ _channelKeys[chan.toLower()] = channels[chan];
+ }
+
+ QHash<QString, QByteArray> bufferCiphers = coreSession()->bufferCiphers(networkId());
+ for (const QString& buffer : bufferCiphers.keys()) {
+ storeChannelCipherKey(buffer.toLower(), bufferCiphers[buffer]);
+ }
+
+ 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, &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, &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);
+ connect(&socket, &QSslSocket::encrypted, this, &CoreNetwork::onSocketInitialized);
+ connect(&socket, selectOverload<const QList<QSslError>&>(&QSslSocket::sslErrors), this, &CoreNetwork::onSslErrors);
+ connect(this, &CoreNetwork::newEvent, coreSession()->eventManager(), &EventManager::postEvent);
+
+ // Custom rate limiting
+ // These react to the user changing settings in the client
+ 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, &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, &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()
+{
+ // 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());
+ }
+}
+
+bool CoreNetwork::forceDisconnect(int msecs)
+{
+ if (socket.state() == QAbstractSocket::UnconnectedState) {
+ // Socket already disconnected.
+ return true;
+ }
+ // Request a socket-level disconnect if not already happened
+ socket.disconnectFromHost();
+ if (socket.state() != QAbstractSocket::UnconnectedState) {
+ return socket.waitForDisconnected(msecs);
+ }
+ return true;
+}
+
+QString CoreNetwork::channelDecode(const QString& bufferName, const QByteArray& string) const
+{
+ if (!bufferName.isEmpty()) {
+ IrcChannel* channel = ircChannel(bufferName);
+ if (channel)
+ return channel->decodeString(string);
+ }
+ return decodeString(string);
+}
+
+QString CoreNetwork::userDecode(const QString& userNick, const QByteArray& string) const
+{
+ IrcUser* user = ircUser(userNick);
+ if (user)
+ return user->decodeString(string);
+ return decodeString(string);
+}
+
+QByteArray CoreNetwork::channelEncode(const QString& bufferName, const QString& string) const
+{
+ if (!bufferName.isEmpty()) {
+ IrcChannel* channel = ircChannel(bufferName);
+ if (channel)
+ return channel->encodeString(string);
+ }
+ return encodeString(string);
+}
+
+QByteArray CoreNetwork::userEncode(const QString& userNick, const QString& string) const
+{
+ IrcUser* user = ircUser(userNick);
+ if (user)
+ return user->encodeString(string);
+ return encodeString(string);
+}
+
+void CoreNetwork::connectToIrc(bool reconnecting)
+{
+ if (_shuttingDown) {
+ return;
+ }
+
+ if (Core::instance()->identServer()) {
+ _socketId = Core::instance()->identServer()->addWaitingSocket();
+ }
+
+ if (_metricsServer) {
+ _metricsServer->addNetwork(userId());
+ }
+
+ if (!reconnecting && useAutoReconnect() && _autoReconnectCount == 0) {
+ _autoReconnectTimer.setInterval(autoReconnectInterval() * 1000);
+ if (unlimitedReconnectRetries())
+ _autoReconnectCount = -1;
+ else
+ _autoReconnectCount = autoReconnectRetries();
+ }
+ if (serverList().isEmpty()) {
+ qWarning() << "Server list empty, ignoring connect request!";
+ return;
+ }
+ CoreIdentity* identity = identityPtr();
+ if (!identity) {
+ qWarning() << "Invalid identity configures, ignoring connect request!";
+ return;
+ }
+
+ // cleaning up old quit reason
+ _quitReason.clear();
+
+ // Reset capability negotiation tracking, also handling server changes during reconnect
+ _capsQueuedIndividual.clear();
+ _capsQueuedBundled.clear();
+ clearCaps();
+ _capNegotiationActive = false;
+ _capInitialNegotiationEnded = false;
+
+ // use a random server?
+ if (useRandomServer()) {
+ _lastUsedServerIndex = qrand() % serverList().size();
+ }
+ else if (_previousConnectionAttemptFailed) {
+ // cycle to next server if previous connection attempt failed
+ _previousConnectionAttemptFailed = false;
+ showMessage(NetworkInternalMessage(
+ Message::Server,
+ BufferInfo::StatusBuffer,
+ "",
+ tr("Connection failed. Cycling to next server...")
+ ));
+ if (++_lastUsedServerIndex >= serverList().size()) {
+ _lastUsedServerIndex = 0;
+ }
+ }
+ else {
+ // Start out with the top server in the list
+ _lastUsedServerIndex = 0;
+ }
+
+ Server server = usedServer();
+ displayStatusMsg(tr("Connecting to %1:%2...").arg(server.host).arg(server.port));
+ showMessage(NetworkInternalMessage(
+ 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);
+ socket.setProxy(proxy);
+ }
+ else {
+ socket.setProxy(QNetworkProxy::NoProxy);
+ }
+
+ 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) {
+ // Avoid hostname lookups when a proxy is specified. The lookups won't use the proxy and may therefore leak the DNS
+ // hostname of the server. Qt's DNS cache also isn't used by the proxy so we don't need to refresh the entry.
+ QHostInfo::fromName(server.host);
+ }
+ if (server.useSsl) {
+ CoreIdentity* identity = identityPtr();
+ if (identity) {
+ socket.setLocalCertificate(identity->sslCert());
+ socket.setPrivateKey(identity->sslKey());
+ }
+ socket.connectToHostEncrypted(server.host, server.port);
+ }
+ else {
+ socket.connectToHost(server.host, server.port);
+ }
+}
+
+void CoreNetwork::disconnectFromIrc(bool requested, const QString& reason, bool withReconnect)
+{
+ // Disconnecting from the network, should expect a socket close or error
+ _disconnectExpected = true;
+ _quitRequested = requested; // see socketDisconnected();
+ if (!withReconnect) {
+ _autoReconnectTimer.stop();
+ _autoReconnectCount = 0; // prohibiting auto reconnect
+ }
+ disablePingTimeout();
+ _msgQueue.clear();
+ if (_metricsServer) {
+ _metricsServer->messageQueue(userId(), 0);
+ }
+
+ IrcUser* me_ = me();
+ if (me_) {
+ QString awayMsg;
+ if (me_->isAway())
+ awayMsg = me_->awayMessage();
+ Core::setAwayMessage(userId(), networkId(), awayMsg);
+ }
+
+ if (reason.isEmpty() && identityPtr())
+ _quitReason = identityPtr()->quitReason();