X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=blobdiff_plain;f=src%2Fcore%2Fircserverhandler.cpp;h=84c6a194796f216d65aeaf6e515d3f1af509986b;hp=19466cb03496e6926aabdb7fabd85de55c90ce62;hb=2d7fa70ddeddb4efdd1c2514a855cf9a6eef39bb;hpb=4af42efb44f286b6d7fd18a05a74dd6d8cace394 diff --git a/src/core/ircserverhandler.cpp b/src/core/ircserverhandler.cpp index 19466cb0..84c6a194 100644 --- a/src/core/ircserverhandler.cpp +++ b/src/core/ircserverhandler.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005-09 by the Quassel Project * + * Copyright (C) 2005-10 by the Quassel Project * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * @@ -32,13 +32,19 @@ #include +#ifdef HAVE_QCA2 +# include "cipher.h" +#endif + IrcServerHandler::IrcServerHandler(CoreNetwork *parent) - : BasicHandler(parent), + : CoreBasicHandler(parent), _whois(false) { + connect(parent, SIGNAL(disconnected(NetworkId)), this, SLOT(destroyNetsplits())); } IrcServerHandler::~IrcServerHandler() { + destroyNetsplits(); } /*! Handle a raw message string sent by the server. We try to find a suitable handler, otherwise we call a default handler. */ @@ -57,11 +63,23 @@ void IrcServerHandler::handleServerMsg(QByteArray msg) { // NOTE: This assumes that this is true in raw encoding, but well, hopefully there are no servers running in japanese on protocol level... int idx = msg.indexOf(" :"); if(idx >= 0) { - if(msg.length() > idx + 2) trailing = msg.mid(idx + 2); + if(msg.length() > idx + 2) + trailing = msg.mid(idx + 2); msg = msg.left(idx); } // OK, now it is safe to split... QList params = msg.split(' '); + + // This could still contain empty elements due to (faulty?) ircds sending multiple spaces in a row + // Also, QByteArray is not nearly as convenient to work with as QString for such things :) + QList::iterator iter = params.begin(); + while(iter != params.end()) { + if(iter->isEmpty()) + iter = params.erase(iter); + else + ++iter; + } + if(!trailing.isEmpty()) params << trailing; if(params.count() < 1) { qWarning() << "Received invalid string from server!"; @@ -70,6 +88,12 @@ void IrcServerHandler::handleServerMsg(QByteArray msg) { QString foo = serverDecode(params.takeFirst()); + // with SASL, the command is 'AUTHENTICATE +' and we should check for this here. + if(foo == QString("AUTHENTICATE +")) { + handleAuthenticate(); + return; + } + // a colon as the first chars indicates the existence of a prefix if(foo[0] == ':') { foo.remove(0, 1); @@ -146,6 +170,11 @@ void IrcServerHandler::defaultHandler(QString cmd, const QString &prefix, const case 321: case 366: case 376: break; + case 903: case 904: case 905: case 906: case 907: + { + network()->putRawLine("CAP END"); + emit displayMsg(Message::Info, BufferInfo::StatusBuffer, "", "CAP: " + params.join("")); + } // Everything else will be marked in red, so we can add them somewhere. default: if(_whois) { @@ -168,15 +197,44 @@ void IrcServerHandler::defaultHandler(QString cmd, const QString &prefix, const //******************************/ // IRC SERVER HANDLER //******************************/ +void IrcServerHandler::handleInvite(const QString &prefix, const QList ¶ms) { + if(!checkParamCount("IrcServerHandler::handleInvite()", params, 2)) + return; +// qDebug() << "IrcServerHandler::handleInvite()" << prefix << params; + + IrcUser *ircuser = network()->updateNickFromMask(prefix); + if(!ircuser) { + return; + } + + QString channel = serverDecode(params[1]); + + emit displayMsg(Message::Invite, BufferInfo::StatusBuffer, "", tr("%1 invited you to channel %2").arg(ircuser->nick()).arg(channel)); +} + void IrcServerHandler::handleJoin(const QString &prefix, const QList ¶ms) { if(!checkParamCount("IrcServerHandler::handleJoin()", params, 1)) return; QString channel = serverDecode(params[0]); IrcUser *ircuser = network()->updateNickFromMask(prefix); - emit displayMsg(Message::Join, BufferInfo::ChannelBuffer, channel, channel, prefix); + + bool handledByNetsplit = false; + if(!_netsplits.empty()) { + foreach(Netsplit* n, _netsplits) { + handledByNetsplit = n->userJoined(prefix, channel); + if(handledByNetsplit) + break; + } + } + + // normal join + if(!handledByNetsplit) { + emit displayMsg(Message::Join, BufferInfo::ChannelBuffer, channel, channel, prefix); + ircuser->joinChannel(channel); + } //qDebug() << "IrcServerHandler::handleJoin()" << prefix << params; - ircuser->joinChannel(channel); + if(network()->isMe(ircuser)) { network()->setChannelJoined(channel); putCmd("MODE", params[0]); // we want to know the modes of the channel we just joined, so we ask politely @@ -237,10 +295,26 @@ void IrcServerHandler::handleMode(const QString &prefix, const QList // user channel modes (op, voice, etc...) if(paramOffset < params.count()) { IrcUser *ircUser = network()->ircUser(params[paramOffset]); - if(add) - channel->addUserMode(ircUser, QString(modes[c])); - else - channel->removeUserMode(ircUser, QString(modes[c])); + if(!ircUser) { + qWarning() << Q_FUNC_INFO << "Unknown IrcUser:" << params[paramOffset]; + } else { + if(add) { + bool handledByNetsplit = false; + if(!_netsplits.empty()) { + foreach(Netsplit* n, _netsplits) { + handledByNetsplit = n->userAlreadyJoined(ircUser->hostmask(), channel->name()); + if(handledByNetsplit) { + n->addMode(ircUser->hostmask(), channel->name(), QString(modes[c])); + break; + } + } + } + if(!handledByNetsplit) + channel->addUserMode(ircUser, QString(modes[c])); + } + else + channel->removeUserMode(ircUser, QString(modes[c])); + } } else { qWarning() << "Received MODE with too few parameters:" << serverDecode(params); } @@ -291,6 +365,10 @@ void IrcServerHandler::handleMode(const QString &prefix, const QList if(!removeModes.isEmpty()) ircUser->removeUserModes(removeModes); + if(network()->isMe(ircUser)) { + network()->updatePersistentModes(addModes, removeModes); + } + // FIXME: redirect emit displayMsg(Message::Mode, BufferInfo::StatusBuffer, "", serverDecode(params).join(" "), prefix); } @@ -327,35 +405,41 @@ void IrcServerHandler::handleNotice(const QString &prefix, const QListisChannelName(target)) { - QString msg = serverDecode(params[1]); - QRegExp welcomeRegExp("^\\[([^\\]]+)\\] "); - if(welcomeRegExp.indexIn(msg) != -1) { - QString channelname = welcomeRegExp.cap(1); - msg = msg.mid(welcomeRegExp.matchedLength()); - CoreIrcChannel *chan = static_cast(network()->ircChannel(channelname)); // we only have CoreIrcChannels in the core, so this cast is safe - if(chan && !chan->receivedWelcomeMsg()) { - chan->setReceivedWelcomeMsg(); - emit displayMsg(Message::Notice, BufferInfo::ChannelBuffer, channelname, msg, prefix); - return; + + QStringList targets = serverDecode(params[0]).split(',', QString::SkipEmptyParts); + QStringList::const_iterator targetIter; + for(targetIter = targets.constBegin(); targetIter != targets.constEnd(); targetIter++) { + QString target = *targetIter; + + // special treatment for welcome messages like: + // :ChanServ!ChanServ@services. NOTICE egst :[#apache] Welcome, this is #apache. Please read the in-channel topic message. This channel is being logged by IRSeekBot. If you have any question please see http://blog.freenode.net/?p=68 + if(!network()->isChannelName(target)) { + QString msg = serverDecode(params[1]); + QRegExp welcomeRegExp("^\\[([^\\]]+)\\] "); + if(welcomeRegExp.indexIn(msg) != -1) { + QString channelname = welcomeRegExp.cap(1); + msg = msg.mid(welcomeRegExp.matchedLength()); + CoreIrcChannel *chan = static_cast(network()->ircChannel(channelname)); // we only have CoreIrcChannels in the core, so this cast is safe + if(chan && !chan->receivedWelcomeMsg()) { + chan->setReceivedWelcomeMsg(); + emit displayMsg(Message::Notice, BufferInfo::ChannelBuffer, channelname, msg, prefix); + continue; + } } } - } - if(prefix.isEmpty() || target == "AUTH") { - target = ""; - } else { - if(!target.isEmpty() && network()->prefixes().contains(target[0])) - target = target.mid(1); - if(!network()->isChannelName(target)) - target = nickFromMask(prefix); + if(prefix.isEmpty() || target == "AUTH") { + target = ""; + } else { + if(!target.isEmpty() && network()->prefixes().contains(target[0])) + target = target.mid(1); + if(!network()->isChannelName(target)) + target = nickFromMask(prefix); + } + + network()->ctcpHandler()->parse(Message::Notice, prefix, target, params[1]); } - network()->ctcpHandler()->parse(Message::Notice, prefix, target, params[1]); } void IrcServerHandler::handlePart(const QString &prefix, const QList ¶ms) { @@ -416,18 +500,26 @@ void IrcServerHandler::handlePrivmsg(const QString &prefix, const QListisChannelName(target)) - target = nickFromMask(prefix); - - // it's possible to pack multiple privmsgs into one param using ctcp - // - > we let the ctcpHandler do the work - network()->ctcpHandler()->parse(Message::Plain, prefix, target, msg); + QStringList targets = serverDecode(params[0]).split(',', QString::SkipEmptyParts); + QStringList::const_iterator targetIter; + for(targetIter = targets.constBegin(); targetIter != targets.constEnd(); targetIter++) { + const QString &target = network()->isChannelName(*targetIter) + ? *targetIter + : senderNick; + +#ifdef HAVE_QCA2 + msg = decrypt(target, msg); +#endif + // it's possible to pack multiple privmsgs into one param using ctcp + // - > we let the ctcpHandler do the work + network()->ctcpHandler()->parse(Message::Plain, prefix, target, msg); + } } void IrcServerHandler::handleQuit(const QString &prefix, const QList ¶ms) { @@ -438,10 +530,32 @@ void IrcServerHandler::handleQuit(const QString &prefix, const QList if(params.count() > 0) msg = userDecode(ircuser->nick(), params[0]); - foreach(QString channel, ircuser->channels()) - emit displayMsg(Message::Quit, BufferInfo::ChannelBuffer, channel, msg, prefix); - - ircuser->quit(); + // check if netsplit + if(Netsplit::isNetsplit(msg)) { + Netsplit *n; + if(!_netsplits.contains(msg)) { + n = new Netsplit(); + connect(n, SIGNAL(finished()), this, SLOT(handleNetsplitFinished())); + connect(n, SIGNAL(netsplitJoin(const QString&, const QStringList&, const QStringList&, const QString&)), + this, SLOT(handleNetsplitJoin(const QString&, const QStringList&, const QStringList&, const QString&))); + connect(n, SIGNAL(netsplitQuit(const QString&, const QStringList&, const QString&)), + this, SLOT(handleNetsplitQuit(const QString&, const QStringList&, const QString&))); + connect(n, SIGNAL(earlyJoin(const QString&, const QStringList&, const QStringList&)), + this, SLOT(handleEarlyNetsplitJoin(const QString&, const QStringList&, const QStringList&))); + _netsplits.insert(msg, n); + } + else { + n = _netsplits[msg]; + } + // add this user to the netsplit + n->userQuit(prefix, ircuser->channels(),msg); + } + // normal quit + else { + foreach(QString channel, ircuser->channels()) + emit displayMsg(Message::Quit, BufferInfo::ChannelBuffer, channel, msg, prefix); + ircuser->quit(); + } } void IrcServerHandler::handleTopic(const QString &prefix, const QList ¶ms) { @@ -457,12 +571,42 @@ void IrcServerHandler::handleTopic(const QString &prefix, const QList 1) - topic = channelDecode(channel->name(), params[1]); + if(params.count() > 1) { + QByteArray rawTopic = params[1]; +#ifdef HAVE_QCA2 + rawTopic = decrypt(channel->name(), rawTopic, true); +#endif + topic = channelDecode(channel->name(), rawTopic); + } channel->setTopic(topic); - emit displayMsg(Message::Server, BufferInfo::ChannelBuffer, channel->name(), tr("%1 has changed topic for %2 to: \"%3\"").arg(ircuser->nick()).arg(channel->name()).arg(topic)); + emit displayMsg(Message::Topic, BufferInfo::ChannelBuffer, channel->name(), tr("%1 has changed topic for %2 to: \"%3\"").arg(ircuser->nick()).arg(channel->name()).arg(topic)); +} + +void IrcServerHandler::handleCap(const QString &prefix, const QList ¶ms) { + // for SASL, there will only be a single param of 'sasl', however you can check here for + // additional CAP messages (ls, multi-prefix, et cetera). + + Q_UNUSED(prefix); + + if(params.size() == 3) { + QString param = serverDecode(params[2]); + if(param == QString("sasl")) { // SASL Ready + network()->putRawLine(serverEncode("AUTHENTICATE PLAIN")); // Only working with PLAIN atm, blowfish later + } + } +} + +void IrcServerHandler::handleAuthenticate() { + QString construct = network()->saslAccount(); + construct.append(QChar(QChar::Null)); + construct.append(network()->saslAccount()); + construct.append(QChar(QChar::Null)); + construct.append(network()->saslPassword()); + QByteArray saslData = QByteArray(construct.toAscii().toBase64()); + saslData.prepend(QString("AUTHENTICATE ").toAscii()); + network()->putRawLine(saslData); } /* RPL_WELCOME */ @@ -506,6 +650,9 @@ void IrcServerHandler::handle005(const QString &prefix, const QList QString value = rawSupport.section("=", 1); network()->addSupport(key, value); } + + /* determine our prefixes here to get an accurate result */ + network()->determinePrefixes(); } /* RPL_UMODEIS - " []" */ @@ -654,7 +801,8 @@ void IrcServerHandler::handle311(const QString &prefix, const QList ircuser->setRealName(serverDecode(params.last())); emit displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("[Whois] %1 is %2 (%3)") .arg(ircuser->nick()).arg(ircuser->hostmask()).arg(ircuser->realName())); } else { - emit displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("[Whois] %1 is %2 (%3)") .arg(serverDecode(params[1])).arg(serverDecode(params[2])).arg(serverDecode(params.last()))); + QString host = QString("%1!%2@%3").arg(serverDecode(params[0])).arg(serverDecode(params[1])).arg(serverDecode(params[2])); + emit displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("[Whois] %1 is %2 (%3)") .arg(serverDecode(params[0])).arg(host).arg(serverDecode(params.last()))); } } @@ -738,7 +886,16 @@ void IrcServerHandler::handle317(const QString &prefix, const QList emit displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("[Whois] %1 is idling for %2 (%3)").arg(ircuser->nick()).arg(secondsToString(ircuser->idleTime().secsTo(now))).arg(ircuser->idleTime().toString())); } else { - emit displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("[Whois] idle message: %1").arg(userDecode(nick, params).join(" "))); + QDateTime now = QDateTime::currentDateTime(); + int idleSecs = serverDecode(params[1]).toInt(); + idleSecs *= -1; + QDateTime idleSince = now.addSecs(idleSecs); + if (params.size() > 3) { // we have a signon time + int loginTime = serverDecode(params[2]).toInt(); + QDateTime datetime = QDateTime::fromTime_t(loginTime); + emit displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("[Whois] %1 is logged in since %2").arg(nick).arg(datetime.toString())); + } + emit displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("[Whois] %1 is idling for %2 (%3)").arg(nick).arg(secondsToString(idleSince.secsTo(now))).arg(idleSince.toString())); } } @@ -861,7 +1018,7 @@ void IrcServerHandler::handle331(const QString &prefix, const QList if(chan) chan->setTopic(QString()); - emit displayMsg(Message::Server, BufferInfo::ChannelBuffer, channel, tr("No topic is set for %1.").arg(channel)); + emit displayMsg(Message::Topic, BufferInfo::ChannelBuffer, channel, tr("No topic is set for %1.").arg(channel)); } /* RPL_TOPIC */ @@ -871,12 +1028,17 @@ void IrcServerHandler::handle332(const QString &prefix, const QList return; QString channel = serverDecode(params[0]); - QString topic = channelDecode(channel, params[1]); + QByteArray rawTopic = params[1]; +#ifdef HAVE_QCA2 + rawTopic = decrypt(channel, rawTopic, true); +#endif + QString topic = channelDecode(channel, rawTopic); + IrcChannel *chan = network()->ircChannel(channel); if(chan) chan->setTopic(topic); - emit displayMsg(Message::Server, BufferInfo::ChannelBuffer, channel, tr("Topic for %1 is \"%2\"").arg(channel, topic)); + emit displayMsg(Message::Topic, BufferInfo::ChannelBuffer, channel, tr("Topic for %1 is \"%2\"").arg(channel, topic)); } /* Topic set by... */ @@ -886,10 +1048,27 @@ void IrcServerHandler::handle333(const QString &prefix, const QList return; QString channel = serverDecode(params[0]); - emit displayMsg(Message::Server, BufferInfo::ChannelBuffer, channel, + emit displayMsg(Message::Topic, BufferInfo::ChannelBuffer, channel, tr("Topic set by %1 on %2") .arg(serverDecode(params[1]), QDateTime::fromTime_t(channelDecode(channel, params[2]).toUInt()).toString())); } +/* RPL_INVITING - " */ +void IrcServerHandler::handle341(const QString &prefix, const QList ¶ms) { + Q_UNUSED(prefix); + if(!checkParamCount("IrcServerHandler::handle341()", params, 2)) + return; + + QString nick = serverDecode(params[0]); + + IrcChannel *channel = network()->ircChannel(serverDecode(params[1])); + if(!channel) { + qWarning() << "IrcServerHandler::handle341(): unknown channel:" << params[1]; + return; + } + + emit displayMsg(Message::Server, BufferInfo::ChannelBuffer, channel->name(), tr("%1 has been invited to %2").arg(nick).arg(channel->name())); +} + /* RPL_WHOREPLY: " ( "H" / "G" > ["*"] [ ( "@" / "+" ) ] : " */ void IrcServerHandler::handle352(const QString &prefix, const QList ¶ms) { @@ -990,6 +1169,91 @@ void IrcServerHandler::handle433(const QString &prefix, const QList tryNextNick(errnick); } +/* ERR_UNAVAILRESOURCE */ +void IrcServerHandler::handle437(const QString &prefix, const QList ¶ms) { + Q_UNUSED(prefix); + if(!checkParamCount("IrcServerHandler::handle437()", params, 1)) + return; + + QString errnick = serverDecode(params[0]); + emit displayMsg(Message::Error, BufferInfo::StatusBuffer, "", tr("Nick/channel is temporarily unavailable: %1").arg(errnick)); + + // if there is a problem while connecting to the server -> we handle it + // but only if our connection has not been finished yet... + if(!network()->currentServer().isEmpty()) + return; + + if(!network()->isChannelName(errnick)) + tryNextNick(errnick); +} + +/* Handle signals from Netsplit objects */ + +void IrcServerHandler::handleNetsplitJoin(const QString &channel, const QStringList &users, const QStringList &modes, const QString& quitMessage) +{ + IrcChannel *ircChannel = network()->ircChannel(channel); + if(!ircChannel) { + return; + } + QList ircUsers; + QStringList newModes = modes; + QStringList newUsers = users; + + foreach(QString user, users) { + IrcUser *iu = network()->ircUser(nickFromMask(user)); + if(iu) + ircUsers.append(iu); + else { // the user already quit + int idx = users.indexOf(user); + newUsers.removeAt(idx); + newModes.removeAt(idx); + } + } + + QString msg = newUsers.join("#:#").append("#:#").append(quitMessage); + emit displayMsg(Message::NetsplitJoin, BufferInfo::ChannelBuffer, channel, msg); + ircChannel->joinIrcUsers(ircUsers, newModes); +} + +void IrcServerHandler::handleNetsplitQuit(const QString &channel, const QStringList &users, const QString& quitMessage) +{ + QString msg = users.join("#:#").append("#:#").append(quitMessage); + emit displayMsg(Message::NetsplitQuit, BufferInfo::ChannelBuffer, channel, msg); + foreach(QString user, users) { + IrcUser *iu = network()->ircUser(nickFromMask(user)); + if(iu) + iu->quit(); + } +} + +void IrcServerHandler::handleEarlyNetsplitJoin(const QString &channel, const QStringList &users, const QStringList &modes) { + IrcChannel *ircChannel = network()->ircChannel(channel); + if(!ircChannel) { + qDebug() << "handleEarlyNetsplitJoin(): channel " << channel << " invalid"; + return; + } + QList ircUsers; + QStringList newModes = modes; + + foreach(QString user, users) { + IrcUser *iu = network()->updateNickFromMask(user); + if(iu) { + ircUsers.append(iu); + emit displayMsg(Message::Join, BufferInfo::ChannelBuffer, channel, channel, user); + } + else { + newModes.removeAt(users.indexOf(user)); + } + } + ircChannel->joinIrcUsers(ircUsers, newModes); +} +void IrcServerHandler::handleNetsplitFinished() +{ + Netsplit* n = qobject_cast(sender()); + _netsplits.remove(_netsplits.key(n)); + n->deleteLater(); +} + /* */ // FIXME networkConnection()->setChannelKey("") for all ERR replies indicating that a JOIN went wrong @@ -1023,7 +1287,22 @@ bool IrcServerHandler::checkParamCount(const QString &methodName, const QListcipher(bufferName); + if(!cipher) + return message_; + QByteArray message = message_; + message = isTopic? cipher->decryptTopic(message) : cipher->decrypt(message); + return message; +} +#endif