X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=blobdiff_plain;f=src%2Fcore%2Fcoresessioneventprocessor.cpp;h=e553e862090849d65c32a625d87dea0cdc50c523;hp=e2edd08389c0d04103e75b66290572ba12a7c9d0;hb=51ced68c9ba7f733c354e0b2dc737caa1f436a47;hpb=af5909677a97f62b20d89229be42701762ae05c3 diff --git a/src/core/coresessioneventprocessor.cpp b/src/core/coresessioneventprocessor.cpp index e2edd083..e553e862 100644 --- a/src/core/coresessioneventprocessor.cpp +++ b/src/core/coresessioneventprocessor.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005-2013 by the Quassel Project * + * Copyright (C) 2005-2015 by the Quassel Project * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * @@ -23,13 +23,20 @@ #include "coreirclisthelper.h" #include "corenetwork.h" #include "coresession.h" +#include "coretransfer.h" +#include "coretransfermanager.h" #include "ctcpevent.h" #include "ircevent.h" #include "ircuser.h" +#include "logger.h" #include "messageevent.h" #include "netsplit.h" #include "quassel.h" +#ifdef HAVE_QCA2 +# include "keyevent.h" +#endif + CoreSessionEventProcessor::CoreSessionEventProcessor(CoreSession *session) : BasicHandler("handleCtcp", session), _coreSession(session) @@ -85,13 +92,16 @@ void CoreSessionEventProcessor::tryNextNick(NetworkEvent *e, const QString &errn void CoreSessionEventProcessor::processIrcEventNumeric(IrcEventNumeric *e) { switch (e->number()) { - // CAP stuff - case 903: - case 904: - case 905: - case 906: - case 907: - qobject_cast(e->network())->putRawLine("CAP END"); + // SASL authentication replies + // See: http://ircv3.net/specs/extensions/sasl-3.1.html + // TODO Handle errors to stop connection if appropriate + case 903: // RPL_SASLSUCCESS + case 904: // ERR_SASLFAIL + case 905: // ERR_SASLTOOLONG + case 906: // ERR_SASLABORTED + case 907: // ERR_SASLALREADY + // Move on to the next capability + sendNextCap(e->network()); break; default: @@ -120,7 +130,7 @@ void CoreSessionEventProcessor::processIrcEventAuthenticate(IrcEvent *e) construct.append(net->saslAccount()); construct.append(QChar(QChar::Null)); construct.append(net->saslPassword()); - QByteArray saslData = QByteArray(construct.toAscii().toBase64()); + QByteArray saslData = QByteArray(construct.toLatin1().toBase64()); saslData.prepend("AUTHENTICATE "); net->putRawLine(saslData); #ifdef HAVE_SSL @@ -130,31 +140,139 @@ void CoreSessionEventProcessor::processIrcEventAuthenticate(IrcEvent *e) #endif } +void CoreSessionEventProcessor::sendNextCap(Network *net) +{ + CoreNetwork *coreNet = qobject_cast(net); + if (coreNet->capNegotiationInProgress()) { + // Request the next capability and remove it from the list + // Handle one at a time so one capability failing won't NAK all of 'em + coreNet->putRawLine(coreNet->serverEncode(QString("CAP REQ :%1").arg(coreNet->takeQueuedCap()))); + } else { + // If SASL requested but not available, print a warning + if (coreNet->networkInfo().useSasl && !coreNet->useCapSASL()) + emit newEvent(new MessageEvent(Message::Error, net, tr("SASL authentication not supported by server, continuing without"), QString(), QString(), Message::None, QDateTime::currentDateTimeUtc())); + + // No pending desired capabilities, end negotiation + coreNet->putRawLine(coreNet->serverEncode(QString("CAP END"))); + emit newEvent(new MessageEvent(Message::Server, net, tr("Capability negotiation finished"), QString(), QString(), Message::None, QDateTime::currentDateTimeUtc())); + } +} void CoreSessionEventProcessor::processIrcEventCap(IrcEvent *e) { - // 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). - - if (e->params().count() == 3) { - if (e->params().at(2).startsWith("sasl")) { // Freenode (at least) sends "sasl " with a trailing space for some reason! - // FIXME use event - // if the current identity has a cert set, use SASL EXTERNAL -#ifdef HAVE_SSL - if (!coreNetwork(e)->identityPtr()->sslCert().isNull()) { - coreNetwork(e)->putRawLine(coreNetwork(e)->serverEncode("AUTHENTICATE EXTERNAL")); + // Handle capability negotiation + // See: http://ircv3.net/specs/core/capability-negotiation-3.2.html + // And: http://ircv3.net/specs/core/capability-negotiation-3.1.html + if (e->params().count() >= 3) { + CoreNetwork *coreNet = coreNetwork(e); + if (e->params().at(1).compare("LS", Qt::CaseInsensitive) == 0) { + // Server: CAP * LS * :multi-prefix extended-join account-notify batch invite-notify tls + // Server: CAP * LS * :cap-notify server-time example.org/dummy-cap=dummyvalue example.org/second-dummy-cap + // Server: CAP * LS :userhost-in-names sasl=EXTERNAL,DH-AES,DH-BLOWFISH,ECDSA-NIST256P-CHALLENGE,PLAIN + bool capListFinished; + QStringList availableCaps; + if (e->params().count() == 4) { + // Middle of multi-line reply, ignore the asterisk + capListFinished = false; + availableCaps = e->params().at(3).split(' '); } else { + // Single line reply + capListFinished = true; + availableCaps = e->params().at(2).split(' '); + } + // We know what capabilities are available, request what we want. + QStringList availableCapPair; + bool queueCurrentCap; + for (int i = 0; i < availableCaps.count(); ++i) { + // Capability may include values, e.g. CAP * LS :multi-prefix sasl=EXTERNAL + availableCapPair = availableCaps[i].trimmed().split('='); + queueCurrentCap = false; + if (availableCapPair.at(0).startsWith("sasl")) { + // Only request SASL if it's enabled + if (coreNet->networkInfo().useSasl) + queueCurrentCap = true; + } else if (availableCapPair.at(0).startsWith("away-notify")) { + // Always request these capabilities if available + queueCurrentCap = true; + } + if (queueCurrentCap) { + if(availableCapPair.count() >= 2) + coreNet->queuePendingCap(availableCapPair.at(0).trimmed(), availableCapPair.at(1).trimmed()); + else + coreNet->queuePendingCap(availableCapPair.at(0).trimmed()); + } + } + // Begin capability requests when capability listing complete + if (capListFinished) { + emit newEvent(new MessageEvent(Message::Server, e->network(), tr("Negotiating capabilities..."), QString(), QString(), Message::None, e->timestamp())); + sendNextCap(coreNet); + } + } else if (e->params().at(1).compare("ACK", Qt::CaseInsensitive) == 0) { + // Server: CAP * ACK :multi-prefix sasl + // Got the capability we want, enable, handle as needed. + // As only one capability is requested at a time, no need to split + // Lower-case the list to make later comparisons easier + // Capability may include values, e.g. CAP * LS :multi-prefix sasl=EXTERNAL + QStringList acceptedCap = e->params().at(2).trimmed().split('='); + + // Mark this cap as accepted + if(acceptedCap.count() >= 2) + coreNet->addCap(acceptedCap.at(0), acceptedCap.at(1)); + else + coreNet->addCap(acceptedCap.at(0)); + + // Handle special cases + if (acceptedCap.at(0).startsWith("sasl")) { + // Freenode (at least) sends "sasl " with a trailing space for some reason! + // if the current identity has a cert set, use SASL EXTERNAL + // FIXME use event + // TODO If value of sasl capability is not empty, limit to accepted +#ifdef HAVE_SSL + if (!coreNet->identityPtr()->sslCert().isNull()) { + coreNet->putRawLine(coreNet->serverEncode("AUTHENTICATE EXTERNAL")); + } else { #endif - // Only working with PLAIN atm, blowfish later - coreNetwork(e)->putRawLine(coreNetwork(e)->serverEncode("AUTHENTICATE PLAIN")); + // Only working with PLAIN atm, blowfish later + coreNet->putRawLine(coreNet->serverEncode("AUTHENTICATE PLAIN")); #ifdef HAVE_SSL - } + } #endif + } else { + // Special handling not needed, move on to next cap + sendNextCap(coreNet); + } + } else if (e->params().at(1).compare("NAK", Qt::CaseInsensitive) == 0) { + // Something went wrong with this capability, disable, go to next cap + // As only one capability is requested at a time, no need to split + // Lower-case the list to make later comparisons easier + QString deniedCap = e->params().at(2).trimmed(); + coreNet->removeCap(deniedCap); + sendNextCap(coreNet); } } } +/* IRCv3 away-notify - ":nick!user@host AWAY [:message]" */ +void CoreSessionEventProcessor::processIrcEventAway(IrcEvent *e) +{ + if (!checkParamCount(e, 2)) + return; + + // Nick is sent as part of parameters in order to split user/server decoding + IrcUser *ircuser = e->network()->ircUser(e->params().at(0)); + if (ircuser) { + if (!e->params().at(1).isEmpty()) { + ircuser->setAway(true); + ircuser->setAwayMessage(e->params().at(1)); + } else { + ircuser->setAway(false); + } + } else { + qDebug() << "Received away-notify data for unknown user" << e->params().at(0); + } +} + void CoreSessionEventProcessor::processIrcEventInvite(IrcEvent *e) { if (checkParamCount(e, 2)) { @@ -182,6 +300,12 @@ void CoreSessionEventProcessor::processIrcEventJoin(IrcEvent *e) break; } + // If using away-notify, check new users. Works around buggy IRC servers + // forgetting to send :away messages for users who join channels when away. + if (coreNetwork(e)->useCapAwayNotify()) { + coreNetwork(e)->queueAutoWhoOneshot(ircuser->nick()); + } + if (!handledByNetsplit) ircuser->joinChannel(channel); else @@ -436,15 +560,47 @@ void CoreSessionEventProcessor::processIrcEventTopic(IrcEvent *e) } -/* RPL_WELCOME */ -void CoreSessionEventProcessor::processIrcEvent001(IrcEvent *e) +#ifdef HAVE_QCA2 +void CoreSessionEventProcessor::processKeyEvent(KeyEvent *e) { - if (!checkParamCount(e, 1)) + if (!Cipher::neededFeaturesAvailable()) { + emit newEvent(new MessageEvent(Message::Error, e->network(), tr("Unable to perform key exchange, missing qca-ossl plugin."), e->prefix(), e->target(), Message::None, e->timestamp())); + return; + } + CoreNetwork *net = qobject_cast(e->network()); + Cipher *c = net->cipher(e->target()); + if (!c) // happens when there is no CoreIrcChannel for the target (i.e. never?) return; - QString myhostmask = e->params().at(0).section(' ', -1, -1); + if (e->exchangeType() == KeyEvent::Init) { + QByteArray pubKey = c->parseInitKeyX(e->key()); + if (pubKey.isEmpty()) { + emit newEvent(new MessageEvent(Message::Error, e->network(), tr("Unable to parse the DH1080_INIT. Key exchange failed."), e->prefix(), e->target(), Message::None, e->timestamp())); + return; + } else { + net->setCipherKey(e->target(), c->key()); + emit newEvent(new MessageEvent(Message::Info, e->network(), tr("Your key is set and messages will be encrypted."), e->prefix(), e->target(), Message::None, e->timestamp())); + QList p; + p << net->serverEncode(e->target()) << net->serverEncode("DH1080_FINISH ")+pubKey; + net->putCmd("NOTICE", p); + } + } else { + if (c->parseFinishKeyX(e->key())) { + net->setCipherKey(e->target(), c->key()); + emit newEvent(new MessageEvent(Message::Info, e->network(), tr("Your key is set and messages will be encrypted."), e->prefix(), e->target(), Message::None, e->timestamp())); + } else { + emit newEvent(new MessageEvent(Message::Info, e->network(), tr("Failed to parse DH1080_FINISH. Key exchange failed."), e->prefix(), e->target(), Message::None, e->timestamp())); + } + } +} +#endif + + +/* RPL_WELCOME */ +void CoreSessionEventProcessor::processIrcEvent001(IrcEventNumeric *e) +{ e->network()->setCurrentServer(e->prefix()); - e->network()->setMyNick(nickFromMask(myhostmask)); + e->network()->setMyNick(e->target()); } @@ -730,8 +886,12 @@ void CoreSessionEventProcessor::processIrcEvent352(IrcEvent *e) ircuser->setRealName(e->params().last().section(" ", 1)); } - if (coreNetwork(e)->isAutoWhoInProgress(channel)) + // Check if channel name has a who in progress. + // If not, then check if user nick exists and has a who in progress. + if (coreNetwork(e)->isAutoWhoInProgress(channel) || + (ircuser && coreNetwork(e)->isAutoWhoInProgress(ircuser->nick()))) { e->setFlag(EventManager::Silent); + } } @@ -754,7 +914,7 @@ void CoreSessionEventProcessor::processIrcEvent353(IrcEvent *e) QStringList nicks; QStringList modes; - foreach(QString nick, e->params()[2].split(' ')) { + foreach(QString nick, e->params()[2].split(' ', QString::SkipEmptyParts)) { QString mode; if (e->network()->prefixes().contains(nick[0])) { @@ -974,9 +1134,70 @@ void CoreSessionEventProcessor::handleCtcpClientinfo(CtcpEvent *e) } +// http://www.irchelp.org/irchelp/rfc/ctcpspec.html +// http://en.wikipedia.org/wiki/Direct_Client-to-Client +void CoreSessionEventProcessor::handleCtcpDcc(CtcpEvent *e) +{ + // DCC support is unfinished, experimental and potentially dangerous, so make it opt-in + if (!Quassel::isOptionSet("enable-experimental-dcc")) { + quInfo() << "DCC disabled, start core with --enable-experimental-dcc if you really want to try it out"; + return; + } + + // normal: SEND [] + // reverse: SEND 0 + QStringList params = e->param().split(' '); + if (params.count()) { + QString cmd = params[0].toUpper(); + if (cmd == "SEND") { + if (params.count() < 4) { + qWarning() << "Invalid DCC SEND request:" << e; // TODO emit proper error to client + return; + } + QString filename = params[1]; + QHostAddress address; + quint16 port = params[3].toUShort(); + quint64 size = 0; + QString numIp = params[2]; // this is either IPv4 as a 32 bit value, or IPv6 (which always contains a colon) + if (numIp.contains(':')) { // IPv6 + if (!address.setAddress(numIp)) { + qWarning() << "Invalid IPv6:" << numIp; + return; + } + } + else { + address.setAddress(numIp.toUInt()); + } + + if (port == 0) { // Reverse DCC is indicated by a 0 port + emit newEvent(new MessageEvent(Message::Error, e->network(), tr("Reverse DCC SEND not supported"), e->prefix(), e->target(), Message::None, e->timestamp())); + return; + } + if (port < 1024) { + qWarning() << "Privileged port requested:" << port; // FIXME ask user if this is ok + } + + + if (params.count() > 4) { // filesize is optional + size = params[4].toULong(); + } + + // TODO: check if target is the right thing to use for the partner + CoreTransfer *transfer = new CoreTransfer(Transfer::Receive, e->target(), filename, address, port, size, this); + coreSession()->signalProxy()->synchronize(transfer); + coreSession()->transferManager()->addTransfer(transfer); + } + else { + emit newEvent(new MessageEvent(Message::Error, e->network(), tr("DCC %1 not supported").arg(cmd), e->prefix(), e->target(), Message::None, e->timestamp())); + return; + } + } +} + + void CoreSessionEventProcessor::handleCtcpPing(CtcpEvent *e) { - e->setReply(e->param()); + e->setReply(e->param().isNull() ? "" : e->param()); }