diff options
author | k4bek4be <34816207+k4bek4be@users.noreply.github.com> | 2022-01-10 16:50:17 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-01-10 15:50:17 +0000 |
commit | 9b0c7929d9fcb158420d1333369bf20e52981f09 (patch) | |
tree | 79091ddbced05193111cc9d8a0f3673dc4716fa6 | |
parent | d895a3aa017f02ceeafddfd54432abb43b9497cf (diff) |
Multiple updates for unreal4 protocol (#285)
* Parse PROTOCTL PREFIX and PROTOCTL USERMODES
* Add several previously unhandled modes
* Fix incorrect SJOIN prefix handling
* Enable message tags sending from ircd
* Add timedban matcher
* Add operclass matcher
* Store all client and channel moddata.
* Call unreal's internal unban on /cs unban
* Check for ~a:0 ban
* Add country ban matcher
* Use +B umode for services bots
-rw-r--r-- | include/modules.h | 9 | ||||
-rw-r--r-- | modules/commands/cs_unban.cpp | 4 | ||||
-rw-r--r-- | modules/protocol/unreal4.cpp | 340 |
3 files changed, 305 insertions, 48 deletions
diff --git a/include/modules.h b/include/modules.h index a9d4a43f5..3da6219bb 100644 --- a/include/modules.h +++ b/include/modules.h @@ -1100,6 +1100,13 @@ class CoreExport Module : public Extensible * @return EVENT_STOP to force the user off of the nick */ virtual EventReturn OnNickValidate(User *u, NickAlias *na) { throw NotImplementedException(); } + + /** Called when a certain user has to be unbanned on a certain channel. + * May be used to send protocol-specific messages. + * @param u The user to be unbanned + * @param c The channel that user has to be unbanned on + */ + virtual void OnChannelUnban(User *u, ChannelInfo *ci) { throw NotImplementedException(); } }; enum Implementation @@ -1125,7 +1132,7 @@ enum Implementation I_OnPrivmsg, I_OnLog, I_OnLogMessage, I_OnDnsRequest, I_OnCheckModes, I_OnChannelSync, I_OnSetCorrectModes, I_OnSerializeCheck, I_OnSerializableConstruct, I_OnSerializableDestruct, I_OnSerializableUpdate, I_OnSerializeTypeCreate, I_OnSetChannelOption, I_OnSetNickOption, I_OnMessage, I_OnCanSet, I_OnCheckDelete, - I_OnExpireTick, I_OnNickValidate, + I_OnExpireTick, I_OnNickValidate, I_OnChannelUnban, I_SIZE }; diff --git a/modules/commands/cs_unban.cpp b/modules/commands/cs_unban.cpp index fe80252d1..93175f308 100644 --- a/modules/commands/cs_unban.cpp +++ b/modules/commands/cs_unban.cpp @@ -44,6 +44,8 @@ class CommandCSUnban : public Command if (!ci->c || !source.AccessFor(ci).HasPriv("UNBAN")) continue; + + FOREACH_MOD(OnChannelUnban, (source.GetUser(), ci)); for (unsigned j = 0; j < modes.size(); ++j) if (ci->c->Unban(source.GetUser(), modes[j]->name, true)) @@ -88,6 +90,8 @@ class CommandCSUnban : public Command bool override = !source.AccessFor(ci).HasPriv("UNBAN") && source.HasPriv("chanserv/kick"); Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to unban " << u2->nick; + FOREACH_MOD(OnChannelUnban, (u2, ci)); + for (unsigned i = 0; i < modes.size(); ++i) ci->c->Unban(u2, modes[i]->name, source.GetUser() == u2); if (u2 == source.GetUser()) diff --git a/modules/protocol/unreal4.cpp b/modules/protocol/unreal4.cpp index 0a71064ba..8ad5f25e6 100644 --- a/modules/protocol/unreal4.cpp +++ b/modules/protocol/unreal4.cpp @@ -13,14 +13,18 @@ #include "modules/cs_mode.h" #include "modules/sasl.h" +typedef Anope::map<Anope::string> ModData; static Anope::string UplinkSID; class UnrealIRCdProto : public IRCDProto { public: - UnrealIRCdProto(Module *creator) : IRCDProto(creator, "UnrealIRCd 4+") + PrimitiveExtensibleItem<ModData> ClientModData; + PrimitiveExtensibleItem<ModData> ChannelModData; + + UnrealIRCdProto(Module *creator) : IRCDProto(creator, "UnrealIRCd 4+"), ClientModData(creator, "ClientModData"), ChannelModData(creator, "ChannelModData") { - DefaultPseudoclientModes = "+Soiq"; + DefaultPseudoclientModes = "+BioqS"; CanSVSNick = true; CanSVSJoin = true; CanSetVHost = true; @@ -226,7 +230,7 @@ class UnrealIRCdProto : public IRCDProto SID = SID/UID mode */ UplinkSocket::Message() << "PASS :" << Config->Uplinks[Anope::CurrentUplink].password; - UplinkSocket::Message() << "PROTOCTL " << "NICKv2 VHP UMODE2 NICKIP SJOIN SJOIN2 SJ3 NOQUIT TKLEXT MLOCK SID"; + UplinkSocket::Message() << "PROTOCTL " << "NICKv2 VHP UMODE2 NICKIP SJOIN SJOIN2 SJ3 NOQUIT TKLEXT MLOCK SID MTAGS"; UplinkSocket::Message() << "PROTOCTL " << "EAUTH=" << Me->GetName() << ",,,Anope-" << Anope::VersionShort(); UplinkSocket::Message() << "PROTOCTL " << "SID=" << Me->GetSID(); SendServer(Me); @@ -552,6 +556,9 @@ namespace UnrealExtban const Anope::string &mask = e->GetMask(); Anope::string real_mask = mask.substr(3); + if (real_mask == "0" && !u->Account()) /* ~a:0 is special and matches all unauthenticated users */ + return true; + return u->Account() && Anope::Match(u->Account()->display, real_mask); } }; @@ -570,6 +577,66 @@ namespace UnrealExtban return !u->fingerprint.empty() && Anope::Match(u->fingerprint, real_mask); } }; + + class OperclassMatcher : public UnrealExtBan + { + public: + OperclassMatcher(const Anope::string &mname, const Anope::string &mbase, char c) : UnrealExtBan(mname, mbase, c) + { + } + + bool Matches(User *u, const Entry *e) anope_override + { + const Anope::string &mask = e->GetMask(); + Anope::string real_mask = mask.substr(3); + ModData *moddata = u->GetExt<ModData>("ClientModData"); + return moddata != NULL && moddata->find("operclass") != moddata->end() && Anope::Match((*moddata)["operclass"], real_mask); + } + }; + + class TimedBanMatcher : public UnrealExtBan + { + public: + TimedBanMatcher(const Anope::string &mname, const Anope::string &mbase, char c) : UnrealExtBan(mname, mbase, c) + { + } + + bool Matches(User *u, const Entry *e) anope_override + { + /* strip down the time (~t:1234:) and call other matchers */ + const Anope::string &mask = e->GetMask(); + Anope::string real_mask = mask.substr(3); + real_mask = real_mask.substr(real_mask.find(":") + 1); + return Entry("BAN", real_mask).Matches(u); + } + }; + + class CountryMatcher : public UnrealExtBan + { + public: + CountryMatcher(const Anope::string &mname, const Anope::string &mbase, char c) : UnrealExtBan(mname, mbase, c) + { + } + + bool Matches(User *u, const Entry *e) anope_override + { + const Anope::string &mask = e->GetMask(); + Anope::string real_mask = mask.substr(3); + ModData *moddata = u->GetExt<ModData>("ClientModData"); + if (moddata == NULL || moddata->find("geoip") == moddata->end()) + return false; + + sepstream sep((*moddata)["geoip"], '|');/* "cc=PL|cd=Poland" */ + Anope::string tokenbuf; + while (sep.GetToken(tokenbuf)) + { + if (tokenbuf.rfind("cc=", 0) == 0) + return (tokenbuf.substr(3, 2) == real_mask); + } + return false; + } + }; + } class ChannelModeFlood : public ChannelModeParam @@ -623,6 +690,45 @@ class ChannelModeFlood : public ChannelModeParam } }; +class ChannelModeHistory : public ChannelModeParam /* stolen from inspircd3's ColonDelimitedParamMode */ +{ + public: + ChannelModeHistory(char modeChar) : ChannelModeParam("HISTORY", modeChar, true) { } + + bool IsValid(Anope::string &value) const anope_override + { + if (value.empty()) + return false; // empty param is never valid + + Anope::string::size_type pos = value.find(':'); + if ((pos == Anope::string::npos) || (pos == 0)) + return false; // no ':' or it's the first char, both are invalid + + Anope::string rest; + try + { + if (convertTo<int>(value, rest, false) <= 0) + return false; // negative numbers and zero are invalid + + rest = rest.substr(1); + int n; + // The part after the ':' is a duration and it + // can be in the user friendly "1d3h20m" format, make sure we accept that + n = Anope::DoTime(rest); + + if (n <= 0) + return false; + } + catch (const ConvertException &e) + { + // conversion error, invalid + return false; + } + + return true; + } +}; + class ChannelModeUnrealSSL : public ChannelMode { public: @@ -646,7 +752,82 @@ struct IRCDMessageCapab : Message::Capab { Anope::string capab = params[i]; - if (capab.find("CHANMODES") != Anope::string::npos) + if (capab.find("USERMODES=") != Anope::string::npos) + { + Anope::string modebuf(capab.begin() + 10, capab.end()); + for (size_t t = 0, end = modebuf.length(); t < end; ++t) + { + switch (modebuf[t]) + { + case 'B': + ModeManager::AddUserMode(new UserMode("BOT", 'B')); + continue; + case 'G': + ModeManager::AddUserMode(new UserMode("CENSOR", 'G')); + continue; + case 'H': + ModeManager::AddUserMode(new UserModeOperOnly("HIDEOPER", 'H')); + continue; + case 'I': + ModeManager::AddUserMode(new UserModeOperOnly("HIDEIDLE", 'I')); + continue; + case 'R': + ModeManager::AddUserMode(new UserMode("REGPRIV", 'R')); + continue; + case 'S': + ModeManager::AddUserMode(new UserModeOperOnly("PROTECTED", 'S')); + continue; + case 'T': + ModeManager::AddUserMode(new UserMode("NOCTCP", 'T')); + continue; + case 'W': + ModeManager::AddUserMode(new UserModeOperOnly("WHOIS", 'W')); + continue; + case 'd': + ModeManager::AddUserMode(new UserMode("DEAF", 'd')); + continue; + case 'D': + ModeManager::AddUserMode(new UserMode("PRIVDEAF", 'D')); + continue; + case 'i': + ModeManager::AddUserMode(new UserMode("INVIS", 'i')); + continue; + case 'o': + ModeManager::AddUserMode(new UserModeOperOnly("OPER", 'o')); + continue; + case 'p': + ModeManager::AddUserMode(new UserMode("PRIV", 'p')); + continue; + case 'q': + ModeManager::AddUserMode(new UserModeOperOnly("GOD", 'q')); + continue; + case 'r': + ModeManager::AddUserMode(new UserModeNoone("REGISTERED", 'r')); + continue; + case 's': + ModeManager::AddUserMode(new UserModeOperOnly("SNOMASK", 's')); + continue; + case 't': + ModeManager::AddUserMode(new UserModeNoone("VHOST", 't')); + continue; + case 'w': + ModeManager::AddUserMode(new UserMode("WALLOPS", 'w')); + continue; + case 'x': + ModeManager::AddUserMode(new UserMode("CLOAK", 'x')); + continue; + case 'z': + ModeManager::AddUserMode(new UserModeNoone("SSL", 'z')); + continue; + case 'Z': + ModeManager::AddUserMode(new UserMode("SSLPRIV", 'Z')); + continue; + default: + ModeManager::AddUserMode(new UserMode("", modebuf[t])); + } + } + } + else if (capab.find("CHANMODES=") != Anope::string::npos) { Anope::string modes(capab.begin() + 10, capab.end()); commasepstream sep(modes); @@ -668,7 +849,9 @@ struct IRCDMessageCapab : Message::Capab ModeManager::AddChannelMode(new UnrealExtban::RegisteredMatcher("REGISTEREDBAN", "BAN", 'R')); ModeManager::AddChannelMode(new UnrealExtban::AccountMatcher("ACCOUNTBAN", "BAN", 'a')); ModeManager::AddChannelMode(new UnrealExtban::FingerprintMatcher("SSLBAN", "BAN", 'S')); - // also has O for opertype extban, but it doesn't send us users opertypes + ModeManager::AddChannelMode(new UnrealExtban::TimedBanMatcher("TIMEDBAN", "BAN", 't')); + ModeManager::AddChannelMode(new UnrealExtban::OperclassMatcher("OPERCLASSBAN", "BAN", 'O')); + ModeManager::AddChannelMode(new UnrealExtban::CountryMatcher("COUNTRYBAN", "BAN", 'C')); continue; case 'e': ModeManager::AddChannelMode(new ChannelModeList("EXCEPT", 'e')); @@ -708,6 +891,9 @@ struct IRCDMessageCapab : Message::Capab case 'l': ModeManager::AddChannelMode(new ChannelModeParam("LIMIT", 'l', true)); continue; + case 'H': + ModeManager::AddChannelMode(new ChannelModeHistory('H')); + continue; default: ModeManager::AddChannelMode(new ChannelModeParam("", modebuf[t], true)); } @@ -779,7 +965,7 @@ struct IRCDMessageCapab : Message::Capab ModeManager::AddChannelMode(new ChannelMode("CENSOR", 'G')); continue; case 'Z': - ModeManager::AddChannelMode(new ChannelModeUnrealSSL("", 'Z')); + ModeManager::AddChannelMode(new ChannelModeUnrealSSL("ALLSSL", 'Z')); continue; case 'd': // post delayed. means that channel is -D but invisible users still exist. @@ -788,7 +974,7 @@ struct IRCDMessageCapab : Message::Capab ModeManager::AddChannelMode(new ChannelMode("DELAYEDJOIN", 'D')); continue; case 'P': - ModeManager::AddChannelMode(new ChannelMode("PERM", 'P')); + ModeManager::AddChannelMode(new ChannelModeOperOnly("PERM", 'P')); continue; default: ModeManager::AddChannelMode(new ChannelMode("", modebuf[t])); @@ -799,6 +985,42 @@ struct IRCDMessageCapab : Message::Capab { UplinkSID = capab.substr(4); } + else if (!capab.find("PREFIX=")) /* PREFIX=(qaohv)~&@%+ */ + { + Anope::string modes(capab.begin() + 7, capab.end()); + reverse(modes.begin(), modes.end()); /* +%@&!)vhoaq( */ + std::size_t mode_count = modes.find(')'); + Anope::string mode_prefixes = modes.substr(0, mode_count); + Anope::string mode_chars = modes.substr(mode_count+1, mode_count); + + for (size_t t = 0, end = mode_chars.length(); t < end; ++t) + { + Anope::string mode_name; + switch (mode_chars[t]) + { + + case 'v': + mode_name = "VOICE"; + break; + case 'h': + mode_name = "HALFOP"; + break; + case 'o': + mode_name = "OP"; + break; + case 'a': + mode_name = "PROTECT"; + break; + case 'q': + mode_name = "OWNER"; + break; + default: + mode_name = ""; + break; + } + ModeManager::AddChannelMode(new ChannelModeStatus(mode_name, mode_chars[t], mode_prefixes[t], t)); + } + } } Message::Capab::Run(source, params); @@ -843,7 +1065,13 @@ struct IRCDMessageChgName : IRCDMessage struct IRCDMessageMD : IRCDMessage { - IRCDMessageMD(Module *creator) : IRCDMessage(creator, "MD", 3) { SetFlag(IRCDMESSAGE_SOFT_LIMIT); } + PrimitiveExtensibleItem<ModData> &ClientModData; + PrimitiveExtensibleItem<ModData> &ChannelModData; + + IRCDMessageMD(Module *creator, PrimitiveExtensibleItem<ModData> &clmoddata, PrimitiveExtensibleItem<ModData> &chmoddata) : IRCDMessage(creator, "MD", 3), ClientModData(clmoddata), ChannelModData(chmoddata) + { + SetFlag(IRCDMESSAGE_SOFT_LIMIT); + } void Run(MessageSource &source, const std::vector<Anope::string> ¶ms) anope_override { @@ -852,13 +1080,25 @@ struct IRCDMessageMD : IRCDMessage &var = params[2], &value = params.size() > 3 ? params[3] : ""; - if (mdtype == "client") + if (mdtype == "client") /* can be a server too! */ { User *u = User::Find(obj); if (u == NULL) return; - + + ModData &clientmd = *ClientModData.Require(u); + + if (value.empty()) + { + clientmd.erase(var); + Log(LOG_DEBUG) << "Erased client moddata " << var << " from " << u->nick; + } + else + { + clientmd[var] = value; + Log(LOG_DEBUG) << "Set client moddata " << var << "=\"" << value << "\" to " << u->nick; + } if (var == "certfp" && !value.empty()) { u->Extend<bool>("ssl"); @@ -866,6 +1106,26 @@ struct IRCDMessageMD : IRCDMessage FOREACH_MOD(OnFingerprint, (u)); } } + else if (mdtype == "channel") + { + Channel *c = Channel::Find(obj); + + if (c == NULL) + return; + + ModData &channelmd = *ChannelModData.Require(c); + + if (value.empty()) + { + channelmd.erase(var); + Log(LOG_DEBUG) << "Erased channel moddata " << var << " from " << c->name; + } + else + { + channelmd[var] = value; + Log(LOG_DEBUG) << "Set channel moddata " << var << "=\"" << value << "\" to " << c->name; + } + } } }; @@ -1125,6 +1385,19 @@ struct IRCDMessageSID : IRCDMessage } }; +static char UnrealSjoinPrefixToModeChar(char sjoin_prefix) +{ + switch(sjoin_prefix) + { + case '*': + return ModeManager::GetStatusChar('~'); + case '~': + return ModeManager::GetStatusChar('&'); + default: + return ModeManager::GetStatusChar(sjoin_prefix); /* remaining are regular */ + } +} + struct IRCDMessageSJoin : IRCDMessage { IRCDMessageSJoin(Module *creator) : IRCDMessage(creator, "SJOIN", 3) { SetFlag(IRCDMESSAGE_REQUIRE_SERVER); SetFlag(IRCDMESSAGE_SOFT_LIMIT); } @@ -1168,7 +1441,7 @@ struct IRCDMessageSJoin : IRCDMessage Message::Join::SJoinUser sju; /* Get prefixes from the nick */ - for (char ch; (ch = ModeManager::GetStatusChar(buf[0]));) + for (char ch; (ch = UnrealSjoinPrefixToModeChar(buf[0]));) { sju.first.AddMode(ch); buf.erase(buf.begin()); @@ -1371,40 +1644,6 @@ class ProtoUnreal : public Module bool use_server_side_mlock; - void AddModes() - { - ModeManager::AddChannelMode(new ChannelModeStatus("VOICE", 'v', '+', 0)); - ModeManager::AddChannelMode(new ChannelModeStatus("HALFOP", 'h', '%', 1)); - ModeManager::AddChannelMode(new ChannelModeStatus("OP", 'o', '@', 2)); - /* Unreal sends +q as * and +a as ~ */ - ModeManager::AddChannelMode(new ChannelModeStatus("PROTECT", 'a', '~', 3)); - ModeManager::AddChannelMode(new ChannelModeStatus("OWNER", 'q', '*', 4)); - - /* Add user modes */ - ModeManager::AddUserMode(new UserMode("BOT", 'B')); - ModeManager::AddUserMode(new UserMode("CENSOR", 'G')); - ModeManager::AddUserMode(new UserModeOperOnly("HIDEOPER", 'H')); - ModeManager::AddUserMode(new UserModeOperOnly("HIDEIDLE", 'I')); - ModeManager::AddUserMode(new UserMode("REGPRIV", 'R')); - ModeManager::AddUserMode(new UserModeOperOnly("PROTECTED", 'S')); - ModeManager::AddUserMode(new UserMode("NOCTCP", 'T')); - ModeManager::AddUserMode(new UserMode("WEBTV", 'V')); - ModeManager::AddUserMode(new UserModeOperOnly("WHOIS", 'W')); - ModeManager::AddUserMode(new UserMode("DEAF", 'd')); - ModeManager::AddUserMode(new UserModeOperOnly("GLOBOPS", 'g')); - ModeManager::AddUserMode(new UserModeOperOnly("HELPOP", 'h')); - ModeManager::AddUserMode(new UserMode("INVIS", 'i')); - ModeManager::AddUserMode(new UserModeOperOnly("OPER", 'o')); - ModeManager::AddUserMode(new UserMode("PRIV", 'p')); - ModeManager::AddUserMode(new UserModeOperOnly("GOD", 'q')); - ModeManager::AddUserMode(new UserModeNoone("REGISTERED", 'r')); - ModeManager::AddUserMode(new UserModeOperOnly("SNOMASK", 's')); - ModeManager::AddUserMode(new UserModeNoone("VHOST", 't')); - ModeManager::AddUserMode(new UserMode("WALLOPS", 'w')); - ModeManager::AddUserMode(new UserMode("CLOAK", 'x')); - ModeManager::AddUserMode(new UserModeNoone("SSL", 'z')); - } - public: ProtoUnreal(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, PROTOCOL | VENDOR), ircd_proto(this), @@ -1413,13 +1652,13 @@ class ProtoUnreal : public Module message_privmsg(this), message_quit(this), message_squit(this), message_stats(this), message_time(this), message_version(this), message_whois(this), - message_capab(this), message_chghost(this), message_chgident(this), message_chgname(this), message_md(this), message_mode(this, "MODE"), + message_capab(this), message_chghost(this), message_chgident(this), message_chgname(this), + message_md(this, ircd_proto.ClientModData, ircd_proto.ChannelModData),message_mode(this, "MODE"), message_svsmode(this, "SVSMODE"), message_svs2mode(this, "SVS2MODE"), message_netinfo(this), message_nick(this), message_pong(this), message_sasl(this), message_sdesc(this), message_sethost(this), message_setident(this), message_setname(this), message_server(this), message_sid(this), message_sjoin(this), message_topic(this), message_uid(this), message_umode2(this) { - this->AddModes(); } void Prioritize() anope_override @@ -1493,6 +1732,13 @@ class ProtoUnreal : public Module return EVENT_CONTINUE; } + + void OnChannelUnban(User *u, ChannelInfo *ci) anope_override + { + UplinkSocket::Message(ci->WhoSends()) << "SVS2MODE " << ci->c->name << " -b " << u->GetUID(); + /* Unreal will remove all matching bans for us regardless of our internal matching. + Don't use Message(Me) here as certain Unreal versions will always respond with TS-less MODE message. */ + } }; MODULE_INIT(ProtoUnreal) |